[Slim-Checkins] r11404 - in /trunk/server: HTML/EN/html/docs/cli-api.html Slim/Control/Queries.pm Slim/Control/Request.pm Slim/Plugin/CLI/Plugin.pm

fred at svn.slimdevices.com fred at svn.slimdevices.com
Sun Feb 11 16:52:54 PST 2007


Author: fred
Date: Sun Feb 11 16:52:54 2007
New Revision: 11404

URL: http://svn.slimdevices.com?rev=11404&view=rev
Log:
Bug: N/A
Description: New query "serverstatus", with a subscribe mode as status.

The subscribe mode is handled in Request.pm as a more generic mechanism.
If all goes well, the subscribe mode of "status" will be handled using
the same mechanism in a couple of days.



Modified:
    trunk/server/HTML/EN/html/docs/cli-api.html
    trunk/server/Slim/Control/Queries.pm
    trunk/server/Slim/Control/Request.pm
    trunk/server/Slim/Plugin/CLI/Plugin.pm

Modified: trunk/server/HTML/EN/html/docs/cli-api.html
URL: http://svn.slimdevices.com/trunk/server/HTML/EN/html/docs/cli-api.html?rev=11404&r1=11403&r2=11404&view=diff
==============================================================================
--- trunk/server/HTML/EN/html/docs/cli-api.html (original)
+++ trunk/server/HTML/EN/html/docs/cli-api.html Sun Feb 11 16:52:54 2007
@@ -71,6 +71,15 @@
 	</li>
 	<li>
 		Added a tag to return the artist from the &quot;<a href="#albums">albums</a>&quot; query.
+	</li>
+	<li>
+		Added the &quot;<a href="#serverstatus">serverstatus</a>&quot; query, to return
+		compound server status in a single query.
+	</li>
+	<li>
+		Slightly reorganised this document to introduce a &quot;Compound queries&quot; section to
+		document queries &quot;<a href="#serverstatus">serverstatus</a>&quot; and 
+		&quot;<a href="#status">status</a>&quot;.
 	</li>
 </ul>
 
@@ -594,24 +603,26 @@
 	Supported Commands
 </h3>
 <p>
-	The supported commands are listed below, grouped by their scope:
+	The available commands and queries are listed below, grouped by their scope:
 </p>
 <ul>
 	<li>
-  		<strong><a href="#GC">General</a></strong>: commands for general
-  		management of the server.
+  		<strong><a href="#GC">General</a></strong>: general management of the Command Line
+  		Interface and of the SlimServer.
   	</li>
 	<li>
-		<strong><a href="#PC">Players</a></strong>: commands to manage and
-  		get information on players.
+		<strong><a href="#PC">Players</a></strong>: management of players.
   	</li>
 	<li>
-		<strong><a href="#DB">Database</a></strong>: commands to manage
-		and browse the music database.
-	</li>
-	<li>
-		<strong><a href="#PL">Playlist</a></strong>: commands to get
-		information on and manage the playlist for each player.
+		<strong><a href="#DB">Database</a></strong>: mangement of the music database.
+	</li>
+	<li>
+		<strong><a href="#PL">Playlist</a></strong>: management of the playlist of each player.
+	</li>
+	<li>
+		<strong><a href="#CQ">Coumpound queries</a></strong>: queries to get
+		most of the information about the server or a player in one convenient query, that can
+		be updated by the server automatically.
 	</li>
 	<li>
 		<strong><a href="#NC">Notifications</a></strong>: internal server
@@ -737,15 +748,16 @@
 </blockquote>
 
 <br>
-<p id="version">
+<p id="version ?">
 	<strong>
 		<code>
 			version
-		</code>
-	</strong>
-</p>
-<p>
-	The &quot;version&quot; query returns the server version number.<
+			?
+		</code>
+	</strong>
+</p>
+<p>
+	The &quot;version&quot; query returns version number of SlimServer.
 </p>
 <p>
 	Examples:
@@ -951,7 +963,8 @@
 	</strong>
 </p>
 <p>
-	The &quot;exit&quot; command closes the TCP connection with the server.
+	The &quot;exit&quot; command closes the TCP connection with the server and terminates the
+	Command Line Interface session.
 </p>
 <p>
 	Example:
@@ -2009,7 +2022,7 @@
 		</td>
 		<td>
 			Number of players known by SlimServer. Equivalent to 
-			&quot;player count ?&quot;.
+			&quot;<a href="#player count ?">player count ?</a>&quot;.
 		</td>
 	</tr>
 	<tr>
@@ -2071,7 +2084,8 @@
 		</td>
 		<td>
 			Player display type. Not returned for streaming connections. 
-			Equivalent to &quot;player displaytype &lt;playerindex&gt; ?&quot;.
+			Equivalent to 
+			&quot;<a href="#player displaytype ?">player displaytype &lt;playerindex&gt; ?</a>&quot;.
 		</td>
 	</tr>
 	<tr>
@@ -2080,7 +2094,7 @@
 		</td>
 		<td>
 			Connected state. Equivalent to 
-			&quot;&lt;playerid&gt; connected ?&quot;.
+			&quot;<a href="#connected ?">&lt;playerid&gt; connected ?</a>&quot;.
 		</td>
 	</tr>
 </table>
@@ -2095,16 +2109,26 @@
 		playerindex:0 playerid:a5:41:d2:cd:cd:05 ip:127.0.0.1:60488 
 		name:127.0.0.1 model:softsqueeze displaytype:graphic-280x16 connected:1 
 		playerindex:1 playerid:00:04:20:02:00:c8 ip:192.168.1.22:3483 
-		name:Movy model:slimp3 displaytype:noritake-katakana connected:1
-		&lt;LF&gt;&quot;
-	</p>
-</blockquote>
-
-<br><p id="alarm"><strong>
-	<code>&lt;playerid&gt; alarm &lt;taggedParameters&gt;</code>
-</strong></p>
-<p>The &quot;alarm&quot; command allows to manipulate player alarms.</p>
-<p>Accepted tagged parameters:</p>
+		name:Movy model:slimp3 displaytype:noritake-katakana connected:1&lt;LF&gt;&quot;
+	</p>
+</blockquote>
+
+<br>
+<p id="alarm">
+	<strong>
+		<code>
+			&lt;playerid&gt;
+			alarm
+			&lt;taggedParameters&gt;
+		</code>
+	</strong>
+</p>
+<p>
+	The &quot;alarm&quot; command allows to manipulate player alarms.
+</p>
+<p>
+	Accepted tagged parameters:
+</p>
 <table border="0" spacing="50">
 	<tr>
 		<td width="100"><b>Tag</b></td>
@@ -2181,11 +2205,11 @@
   <p><b>Defining a new alarm</b>
 <br>Request: &quot;bd:a5:a9:9b:9d:df alarm cmd:set dow:1 enable:1 
 	playlist_id:22 time:9000&lt;LF&gt;&quot;
-<br>Response: &quot;bd:a5:a9:9b:9d:df alarm cmd:update dow:1 enable:1 
+<br>Response: &quot;bd:a5:a9:9b:9d:df alarm cmd:set dow:1 enable:1 
 	playlist_id:22 time:9000 count:1&lt;LF&gt;&quot;</p>
   <p><b>Clearing an alarm</b>
 <br>Request: &quot;bd:a5:a9:9b:9d:df alarm cmd:clear dow:1&lt;LF&gt;&quot;
-<br>Response: &quot;bd:a5:a9:9b:9d:df alarm cmd:update dow:1 count:1
+<br>Response: &quot;bd:a5:a9:9b:9d:df alarm cmd:clear dow:1 count:1
 	&lt;LF&gt;&quot;</p>
   <p><b>Enabling a previously defined alarm</b>
 <br>Request: &quot;bd:a5:a9:9b:9d:df alarm cmd:update dow:1 enable:1&lt;LF&gt;&quot;
@@ -4814,9 +4838,6 @@
 	</li>
 	<li>
 		<strong><code><a href="#playlistcontrol">playlistcontrol</a></code></strong>
-	</li>
-	<li>
-		<strong><code><a href="#status">status</a></code></strong>
 	</li>
 </ul>
 
@@ -5602,12 +5623,30 @@
   	 	cmd:load album_id:22 count:12&lt;LF&gt;&quot;</p>
 </blockquote>
 
-<br>
-<p id="status">
-	<strong>
-		<code>
-			&lt;playerid&gt;
-			status 
+
+<!----------------------------------------------------------------------------->
+<!-- COMPOUND QUERIES                                                        -->
+<!----------------------------------------------------------------------------->
+
+<hr>
+<h2 id="CQ">
+	Compound queries
+</h2>
+<ul>
+	<li>
+		<strong><code><a href="#serverstatus">serverstatus</a></code></strong>
+	</li>
+	<li>
+		<strong><code><a href="#status">status</a></code></strong>
+	</li>
+</ul>
+
+
+<br>
+<p id="serverstatus">
+	<strong>
+		<code>
+			serverstatus 
 			&lt;start&gt; 
 			&lt;itemsPerResponse&gt; 
 			&lt;taggedParameters&gt;
@@ -5615,25 +5654,14 @@
 	</strong>
 </p>
 <p>
-	The &quot;status&quot; request returns the complete status about a given
-	player, including the current playlist. Set the &lt;start&gt; parameter to
-	&quot;-&quot; to get the playlist data starting from the current song.
+	The &quot;serverstatus&quot; query returns a complete status about the server, including its 
+	players. 
 	<br>
-	In this &quot;curent&quot; mode and if repeat is on, SlimServer will attempt
-	to return &lt;itemsPerResponse&gt; elements, by repeating the playlist at
-	most once, unless shuffling is on and SlimServer is configured to re-shuffle
-	the playlist at each loop (in which case it is impossible to predict the
-	song following the last one in the playlist until this last song has
-	finished playing).
-	<br>
-	Similarly, in the &quot;curent&quot; mode, if repeat
-	is one, only the current song is returned, regardless of the value of
-	&lt;itemsPerResponse&gt;.
-	<br>
-	Clients can subscribe to &quot;status&quot; requests, so that the request
+	Clients can subscribe to &quot;serverstatus&quot; queries, so that the query
 	results are automatically returned asynchronously whenever a change occurs
-	to a player. Please note this mechanism is completely distinct from the 
-	&quot;listen&quot; and &quot;subscribe&quot; commands described above.
+	to the server. Please note this mechanism is completely distinct from the 
+	&quot;listen&quot; and &quot;subscribe&quot; commands described elsewhere in this
+	document.
 </p>
 <p>
 	Accepted tagged parameters:
@@ -5721,6 +5749,260 @@
 			rescan
 		</td>
 		<td>
+			Returned with value 1 if SlimServer is still scanning the
+			database. The results may therefore be incomplete. Not returned if
+			no scan is in progress.
+		</td>
+	</tr>
+	<tr>
+		<td>
+			version
+		</td>
+		<td>
+			SlimServer version. Equivalent to
+			&quot;<a href="#version ?">version ?</a>&quot;
+		</td>
+	</tr>
+	<tr>
+		<td>
+			info total albums
+		</td>
+		<td>
+			Number of albums known to SlimServer. Equivalent to
+			&quot;<a href="#info total albums ?">info total albums ?</a>&quot;
+		</td>
+	</tr>
+	<tr>
+		<td>
+			info total artists
+		</td>
+		<td>
+			Number of artists known to SlimServer. Equivalent to
+			&quot;<a href="#info total artists ?">info total artists ?</a>&quot;
+		</td>
+	</tr>
+	<tr>
+		<td>
+			info total genres
+		</td>
+		<td>
+			Number of genres known to SlimServer. Equivalent to
+			&quot;<a href="#info total genres ?">info total genres ?</a>&quot;
+		</td>
+	</tr>
+	<tr>
+		<td>
+			info total songs
+		</td>
+		<td>
+			Number of songs known to SlimServer. Equivalent to
+			&quot;<a href="#info total songs ?">info total songs ?</a>&quot;
+		</td>
+	</tr>
+	<tr>
+		<td>
+			player count
+		</td>
+		<td>
+			Number of players known by SlimServer. Equivalent to 
+			&quot;<a href="#player count ?">player count ?</a>&quot;.
+		</td>
+	</tr>
+	<tr>
+		<td>
+			<em>
+				For each player:
+			</em>
+		</td>
+		<td>
+			<em>
+				Essentially, this list is equivalent to the one returned by
+				&quot;<a href="#players">players</a>&quot;.
+			</em>
+		</td>
+	</tr>
+	<tr>
+		<td>
+			&nbsp;&nbsp;<b>playerid</b>
+		</td>
+		<td>
+			Player unique identifier. Item delimiter. Equivalent to 
+			&quot;<a href="#player id ?">player id ?</a>&quot;.
+		</td>
+	</tr>
+	<tr>
+		<td>
+			&nbsp;&nbsp;ip
+		</td>
+		<td>
+			Player IP and port. Equivalent to 
+			&quot;<a href="#player ip ?">player ip ?</a>&quot;.
+		</td>
+	</tr>
+	<tr>
+		<td>
+			&nbsp;&nbsp;name
+		</td>
+		<td>
+			Player name. Equivalent to 
+			&quot;<a href="#player name ?">player name ?</a>&quot;.
+		</td>
+	</tr>
+	<tr>
+		<td>
+			&nbsp;&nbsp;model
+		</td>
+		<td>
+			Player model. Equivalent to 
+			&quot;<a href="#player model ?">player model ?</a>&quot;.
+		</td>
+	</tr>
+	<tr>
+		<td>
+			&nbsp;&nbsp;displaytype
+		</td>
+		<td>
+			Player display type. Not returned for streaming connections. 
+			Equivalent to 
+			&quot;<a href="#player displaytype ?">player displaytype &lt;playerindex&gt; ?</a>&quot;.
+		</td>
+	</tr>
+	<tr>
+		<td>
+			&nbsp;&nbsp;connected
+		</td>
+		<td>
+			Connected state. Equivalent to 
+			&quot;<a href="#connected ?">&lt;playerid&gt; connected ?</a>&quot;.
+		</td>
+	</tr>
+</table>
+<p>
+	Example:
+</p>
+<blockquote> 
+</blockquote>
+
+
+<br>
+<p id="status">
+	<strong>
+		<code>
+			&lt;playerid&gt;
+			status 
+			&lt;start&gt; 
+			&lt;itemsPerResponse&gt; 
+			&lt;taggedParameters&gt;
+		</code>
+	</strong>
+</p>
+<p>
+	The &quot;status&quot; query returns the complete status about a given
+	player, including the current playlist. Set the &lt;start&gt; parameter to
+	&quot;-&quot; to get the playlist data starting from the current song.
+	<br>
+	In this &quot;curent&quot; mode and if repeat is on, SlimServer will attempt
+	to return &lt;itemsPerResponse&gt; elements, by repeating the playlist at
+	most once, unless shuffling is on and SlimServer is configured to re-shuffle
+	the playlist at each loop (in which case it is impossible to predict the
+	song following the last one in the playlist until this last song has
+	finished playing).
+	<br>
+	Similarly, in the &quot;curent&quot; mode, if repeat
+	is one, only the current song is returned, regardless of the value of
+	&lt;itemsPerResponse&gt;.
+	<br>
+	Clients can subscribe to &quot;status&quot; queries, so that the query
+	results are automatically returned asynchronously whenever a change occurs
+	to a player. Please note this mechanism is completely distinct from the 
+	&quot;listen&quot; and &quot;subscribe&quot; commands described elsewhere in this document.
+</p>
+<p>
+	Accepted tagged parameters:
+</p>
+<table border="0" spacing="50">
+	<tr>
+		<td width="150">
+			<b>Tag</b>
+		</td>
+		<td>
+			<b>Description</b>
+		</td>
+	</tr>
+	<tr>
+		<td>
+			tags
+		</td>
+		<td>
+			Determines which tags are returned. Each returned tag is identified
+			by a letter (see command 
+			&quot;<a href="#songinfo">songinfo</a>&quot; for a list of possible
+			fields and their identifying letter). The default tags value for 
+			this query is &quot;gald&quot;.
+		</td>
+	</tr>
+	<tr>
+		<td>
+			charset
+		</td>
+		<td>
+			Character set to use to return data. See 
+			<a href="#Notes">charset notes</a> above.
+		</td>
+	</tr>
+	<tr>
+		<td>
+			subscribe
+		</td>
+		<td>
+			This optional parameter controls the subscription to the player 
+			status. Only one status subscription is possible per player and 
+			connection.
+			<br>
+			Subscription is enabled by using this parameter with a positive
+			integer. It is disabled by using &quot;-&quot;. When the
+			subscription is enabled, normal &quot;status&quot; queries (i.e.
+			not using the &quot;subscribe&quot; parameter) can be performed and
+			will have no effect on the subscription in place.
+			<br>
+			When enabled, the &quot;status&quot; request is automatically
+			re-generated on player change (and sent asynchronously to the CLI
+			client). The number indicates the time interval in seconds between 
+			automatic generations in case nothing happened to the player in the
+			interval. Use &quot;0&quot; to disable this last feature and only be
+			notified on player or playlist changes. Please see the example.
+			<br>
+			Some situations will lead to multiple status queries
+			generated very close to another. This is a limitation of the 
+			change detection &quot;algorithm&quot;.
+			<br>
+			If the player is manually (through the web page) or automatically
+			deleted from SlimServer, &quot;client forget&quot; is sent instead
+			of the status query and the subscription is terminated.
+			<br>
+			Please note this mechanism is completely distinct from the 
+			&quot;listen&quot; and &quot;subscribe&quot; commands described 
+			above.
+		</td>
+	</tr>
+</table>
+<p>
+	Returned tagged parameters:
+</p>
+<table border="0" spacing="50">
+	<tr>
+		<td width="150">
+			<b>Tag</b>
+		</td>
+		<td>
+			<b>Description</b>
+		</td>
+	</tr>
+	<tr>
+		<td>
+			rescan
+		</td>
+		<td>
 			Returned with value 1 if the SlimServer is still scanning the
 			database. The results may therefore be incomplete. Not returned if
 			no scan is in progress.

Modified: trunk/server/Slim/Control/Queries.pm
URL: http://svn.slimdevices.com/trunk/server/Slim/Control/Queries.pm?rev=11404&r1=11403&r2=11404&view=diff
==============================================================================
--- trunk/server/Slim/Control/Queries.pm (original)
+++ trunk/server/Slim/Control/Queries.pm Sun Feb 11 16:52:54 2007
@@ -23,7 +23,8 @@
 L<Slim::Control::Queries> implements most SlimServer queries and is designed to 
  be exclusively called through Request.pm and the mechanisms it defines.
 
- There are no important differences between the code for a query and one for
+ Except for subscribe-able queries (such as status and serverstatus), there are no
+ important differences between the code for a query and one for
  a command. Please check the commented command in Commands.pm.
 
 =cut
@@ -39,6 +40,7 @@
 use Slim::Utils::Unicode;
 
 my $log = logger('control.queries');
+
 
 sub alarmsQuery {
 	my $request = shift;
@@ -844,6 +846,7 @@
 	$request->setStatusDone();
 }
 
+
 sub playlistPlaylistsinfoQuery {
 	my $request = shift;
 	
@@ -874,6 +877,7 @@
 	
 	$request->setStatusDone();
 }
+
 
 sub playlistXQuery {
 	my $request = shift;
@@ -1251,6 +1255,85 @@
 
 			$loopCount++;
 		}
+	}
+	
+	$request->setStatusDone();
+}
+
+
+sub serverstatusQuery {
+	my $request = shift;
+	
+	$log->debug("Begin Function");
+
+	# check this is the correct query
+	if ($request->isNotQuery([['serverstatus']])) {
+		$request->setStatusBadDispatch();
+		return;
+	}
+	
+ 	if (Slim::Music::Import->stillScanning()) {
+ 		$request->addResult('rescan', "1");
+ 	}
+ 	
+ 	# add version
+ 	$request->addResult('version', $::VERSION);
+
+	# add totals
+	$request->addResult("info total albums", Slim::Schema->count('Album'));
+	$request->addResult("info total artists", Slim::Schema->rs('Contributor')->browse->count);
+	$request->addResult("info total genres", Slim::Schema->count('Genre'));
+	$request->addResult("info total songs", Slim::Schema->rs('Track')->browse->count);
+
+	# get our parameters
+	my $index    = $request->getParam('_index');
+	my $quantity = $request->getParam('_quantity');
+	
+	my $count = Slim::Player::Client::clientCount();
+	$request->addResult('player count', $count);
+
+	my ($valid, $start, $end) = $request->normalize(scalar($index), scalar($quantity), $count);
+
+	if ($valid) {
+		my $cnt = 0;
+		my @players = Slim::Player::Client::clients();
+
+		if (scalar(@players) > 0) {
+
+			for my $eachclient (@players[$start..$end]) {
+				$request->addResultLoop('@players', $cnt, 
+					'playerid', $eachclient->id());
+				$request->addResultLoop('@players', $cnt, 
+					'ip', $eachclient->ipport());
+				$request->addResultLoop('@players', $cnt, 
+					'name', $eachclient->name());
+				$request->addResultLoop('@players', $cnt, 
+					'model', $eachclient->model());
+				$request->addResultLoop('@players', $cnt, 
+					'displaytype', $eachclient->vfdmodel())
+					unless ($eachclient->model() eq 'http');
+				$request->addResultLoop('@players', $cnt, 
+					'connected', ($eachclient->connected() || 0));
+					
+				$cnt++;
+			}	
+		}
+	}
+	
+	# manage the subscription
+	if (defined(my $timeout = $request->getParam('subscribe'))) {
+	
+		# the filter function decides, based on a notified request, if the serverstatus
+		# query must be re-executed.
+		sub filter{
+			my $request = shift;
+						
+			# we want to know about rescan and all client notifs
+			return $request->isCommand([['rescan', 'client']]);
+		}
+	
+		# register ourselves to be automatically re-executed on timeout or filter
+		$request->registerAutoExecute($timeout, \&filter);
 	}
 	
 	$request->setStatusDone();
@@ -1615,6 +1698,7 @@
 	$request->setStatusDone();
 }
 
+
 sub timeQuery {
 	my $request = shift;
 	
@@ -1750,6 +1834,7 @@
 	$request->setStatusDone();
 }
 
+
 sub versionQuery {
 	my $request = shift;
 	
@@ -1767,6 +1852,7 @@
 	
 	$request->setStatusDone();
 }
+
 
 sub yearsQuery {
 	my $request = shift;
@@ -1941,6 +2027,7 @@
 	# add it directly to the result loop
 	$request->setResultLoopHash($loop, $index, $hashRef);
 }
+
 
 sub _songData {
 	my $pathOrObj = shift; # song path or object

Modified: trunk/server/Slim/Control/Request.pm
URL: http://svn.slimdevices.com/trunk/server/Slim/Control/Request.pm?rev=11404&r1=11403&r2=11404&view=diff
==============================================================================
--- trunk/server/Slim/Control/Request.pm (original)
+++ trunk/server/Slim/Control/Request.pm Sun Feb 11 16:52:54 2007
@@ -149,12 +149,16 @@
  Y    playlist        path                        <index>                     ?
  Y    playlist        remote                      <index>                     ?
  
- Y    status          <startindex>                <numitems>                  <tagged parameters>
- 
  Y    playlist        name                        ?
  Y    playlist        url                         ?
  Y    playlist        modified                    ?
  Y    playlist        playlistsinfo               <tagged parameters>
+ 
+ COMPOUND
+ 
+ N    serverstatus    <startindex>                <numitems>                  <tagged parameters>
+ Y    status          <startindex>                <numitems>                  <tagged parameters>
+ 
  
  DEPRECATED (BUT STILL SUPPORTED)
  Y    mode            <play|pause|stop>
@@ -413,9 +417,12 @@
 our %dispatchDB;                # contains a multi-level hash pointing to
                                 # each command or query subroutine
 
-our %subscribers = ();          # contains the clients to the notification
-                                # mechanism
-
+our %listeners = ();            # contains the clients to the notification
+                                # mechanism (internal to the server)
+
+our %subscribers = ();          # contains the requests being subscribed to
+                                # (generaly by external users/clients)
+                                
 our @notificationQueue;         # contains the Requests waiting to be notified
 
 our $requestTask = Slim::Utils::PerfMon->new('Request Task', [0.002, 0.005, 0.010, 0.015, 0.025, 0.050, 0.1, 0.5, 1, 5]);
@@ -549,6 +556,7 @@
     addDispatch(['rescan',         '?'],                                                               [0, 1, 0, \&Slim::Control::Queries::rescanQuery]);
     addDispatch(['rescan',         '_playlists'],                                                      [0, 0, 0, \&Slim::Control::Commands::rescanCommand]);
     addDispatch(['search',         '_index',         '_quantity'],                                     [0, 1, 1, \&Slim::Control::Queries::searchQuery]);
+    addDispatch(['serverstatus',   '_index',         '_quantity'],                                     [0, 1, 1, \&Slim::Control::Queries::serverstatusQuery]);
     addDispatch(['show'],                                                                              [1, 0, 1, \&Slim::Control::Commands::showCommand]);
     addDispatch(['signalstrength', '?'],                                                               [1, 1, 0, \&Slim::Control::Queries::signalstrengthQuery]);
     addDispatch(['sleep',          '?'],                                                               [1, 1, 0, \&Slim::Control::Queries::sleepQuery]);
@@ -691,12 +699,12 @@
 	my $subscriberFuncRef = shift || return;
 	my $requestsRef = shift;
 	
-	$subscribers{$subscriberFuncRef} = [$subscriberFuncRef, $requestsRef];
+	$listeners{$subscriberFuncRef} = [$subscriberFuncRef, $requestsRef];
 	
 	$log->info(sprintf(
-		"Request from: %s - (%d subscribers)\n",
+		"Request from: %s - (%d listeners)\n",
 		Slim::Utils::PerlRunTime::realNameForCodeRef($subscriberFuncRef),
-		scalar(keys %subscribers)
+		scalar(keys %listeners)
 	));
 }
 
@@ -704,16 +712,16 @@
 sub unsubscribe {
 	my $subscriberFuncRef = shift;
 	
-	delete $subscribers{$subscriberFuncRef};
+	delete $listeners{$subscriberFuncRef};
 
 	$log->info(sprintf(
-		"Request from: %s - (%d subscribers)\n",
+		"Request from: %s - (%d listeners)\n",
 		Slim::Utils::PerlRunTime::realNameForCodeRef($subscriberFuncRef),
-		scalar(keys %subscribers)
+		scalar(keys %listeners)
 	));
 }
 
-# notify subscribers from an array, useful for notifying w/o execution
+# notify listeners from an array, useful for notifying w/o execution
 # (requests must, however, be defined in the dispatch table)
 sub notifyFromArray {
 	my $client         = shift;     # client, if any, to which the query applies
@@ -763,6 +771,29 @@
 	return $request;
 }
 
+=head2 unregisterAutoExecute ( $connectionID )
+
+Removes all subscriptions for this $connectionID.
+
+=cut
+sub unregisterAutoExecute{
+	my $connectionID = shift;
+	
+	# kill any timers
+	for my $name (keys %{$subscribers{$connectionID}}) {
+		for my $clientid (keys %{$subscribers{$connectionID}{$name}}) {
+			
+			my $request = $subscribers{$connectionID}{$name}{$clientid};
+			
+			Slim::Utils::Timers::killTimers($request, \&__autoexecute);
+		}
+	}
+	
+	# delete everything linked to connection
+	delete $subscribers{$connectionID};
+}
+
+
 ################################################################################
 # Constructors
 ################################################################################
@@ -777,20 +808,23 @@
 	tie (my %resultHash, "Tie::LLHash", {lazy => 1});
 	
 	my $self = {
-		'_request'    => [],
-		'_isQuery'    => undef,
-		'_clientid'   => $clientid,
-		'_needClient' => 0,
-		'_params'     => \%paramHash,
-		'_curparam'   => 0,
-		'_status'     => 0,
-		'_results'    => \%resultHash,
-		'_func'       => undef,
-		'_cb_enable'  => 1,
-		'_cb_func'    => undef,
-		'_cb_args'    => undef,
-		'_source'     => undef,
-		'_private'    => undef,
+		'_request'           => [],
+		'_isQuery'           => undef,
+		'_clientid'          => $clientid,
+		'_needClient'        => 0,
+		'_params'            => \%paramHash,
+		'_curparam'          => 0,
+		'_status'            => 0,
+		'_results'           => \%resultHash,
+		'_func'              => undef,
+		'_cb_enable'         => 1,
+		'_cb_func'           => undef,
+		'_cb_args'           => undef,
+		'_source'            => undef,
+		'_connectionid'      => undef,
+		'_ae_callback'       => undef,
+		'_ae_filter'         => undef,
+		'_private'           => undef,
 	};
 
 	bless $self, $class;
@@ -817,6 +851,9 @@
 	$copy->{'_func'} = \&{$self->{'_func'}};
 	$copy->{'_source'} = $self->{'_source'};
 	$copy->{'_private'} = $self->{'_private'};
+	$copy->{'_connectionid'} = $self->{'_connectionid'};
+	$copy->{'_ae_callback'} = $self->{'_ae_callback'};
+	$copy->{'_ae_filter'} = $self->{'_ae_filter'};
 	$copy->{'_curparam'} = $self->{'_curparam'};
 	
 	# duplicate the arrays and hashes
@@ -940,6 +977,37 @@
 	
 	return $self->{'_source'};
 }
+
+# sets/returns the source connectionid
+sub connectionID {
+	my $self = shift;
+	my $newvalue = shift;
+	
+	$self->{'_connectionid'} = $newvalue if defined $newvalue;
+	
+	return $self->{'_connectionid'};
+}
+
+# sets/returns the source subscribe callback
+sub autoExecuteCallback {
+	my $self = shift;
+	my $newvalue = shift;
+	
+	$self->{'_ae_callback'} = $newvalue if defined $newvalue;
+	
+	return $self->{'_ae_callback'};
+}
+
+# sets/returns the source subscribe callback
+sub autoExecuteFilter {
+	my $self = shift;
+	my $newvalue = shift;
+	
+	$self->{'_ae_filter'} = $newvalue if defined $newvalue && ref($newvalue) eq 'CODE';
+	
+	return $self->{'_ae_filter'};
+}
+
 
 # sets/returns the source private data
 sub privateData {
@@ -1602,21 +1670,20 @@
 	}
 }
 
-# notify subscribers...
+# notify listeners...
 sub notify {
 	my $self = shift || return;
-	my $dontcallExecuteCallback = shift;
-
-	for my $subscriber (keys %subscribers) {
-
-		if ( $subscribers{$subscriber} ) {
+
+	for my $listener (keys %listeners) {
+
+		if ( $listeners{$listener} ) {
 
 			# filter based on desired requests
 			# undef means no filter
-			my $notifyFuncRef = $subscribers{$subscriber}->[0];
-			my $requestsRef   = $subscribers{$subscriber}->[1];
-
-			my $funcName = $subscriber;
+			my $notifyFuncRef = $listeners{$listener}->[0];
+			my $requestsRef   = $listeners{$listener}->[1];
+
+			my $funcName = $listener;
 
 			if ($log->is_debug && ref($notifyFuncRef) eq 'CODE') {
 				$funcName = Slim::Utils::PerlRunTime::realNameForCodeRef($notifyFuncRef);
@@ -1650,9 +1717,98 @@
 
 		}
 	}
-}
-
-# handle encoding for external commands
+	
+	# handle subscriptions
+	# send the notification to all filters...
+	for my $cnxid (keys %subscribers) {
+		for my $name (keys %{$subscribers{$cnxid}}) {
+			for my $clientid (keys %{$subscribers{$cnxid}{$name}}) {
+				
+				my $request = $subscribers{$cnxid}{$name}{$clientid};
+				
+				my $relevant = 1;
+				
+				if (defined(my $funcPtr = $request->autoExecuteFilter())) {
+				
+					$relevant = 0;
+					
+					if (ref($funcPtr) eq 'CODE') {
+				
+						eval { $relevant = &{$funcPtr}($self) };
+				
+						if ($@) {
+							my $funcName = Slim::Utils::PerlRunTime::realNameForCodeRef($funcPtr);
+							logError("While trying to run function coderef [$funcName]: [$@]");
+							next;
+						}
+					}
+				}
+				
+				if ($relevant) {
+					$request->__autoexecute();
+				}
+			}
+		}
+	}
+}
+
+
+=head2 registerAutoExecute ( timeout, filterFunc )
+
+Register ourself as subscribed to. Autoexecute after timeout
+(if > 0) or if filterFunc returns 1 when sent notifications.
+
+=cut
+sub registerAutoExecute{
+	my $self = shift || return;
+	my $timeout = shift;
+	my $filterFunc = shift;
+	
+	# we shall be a query
+	return unless $self->{'_isQuery'};
+	
+	# we shall have a defined connectionID
+	my $cnxid = $self->connectionID() || return;
+	
+	# requests with a client are remembered by client
+	my $clientid = $self->clientid() || 'global';
+	
+	# requests are remembered by kind
+	my $name = $self->getRequestString();
+	
+	# store the filterFunc in the request
+	$self->autoExecuteFilter($filterFunc);
+	
+	# kill any previous subscription we might have laying around 
+	# (for this query/client/connection)
+	my $oldrequest = $subscribers{$cnxid}{$name}{$clientid};
+
+	delete $subscribers{$cnxid}{$name}{$clientid};
+
+	Slim::Utils::Timers::killTimers($oldrequest, \&__autoexecute);
+
+	# store the new subscription if this is what is asked of us
+	if ($timeout ne '-') {
+		
+		# copy the request
+		my $request = $self->virginCopy();
+
+		$subscribers{$cnxid}{$name}{$clientid} = $request;
+
+		if ($timeout > 0) {
+			# start the timer
+			Slim::Utils::Timers::setTimer($request, 
+				Time::HiRes::time() + $timeout,
+				\&__autoexecute);
+		}
+	}
+}
+
+=head2 fixencoding ( )
+
+Handle encoding for external commands.
+
+=cut
 sub fixEncoding {
 	my $self = shift || return;
 	
@@ -2057,6 +2213,40 @@
 	}
 }
 
+# callback for the subscriptions.
+sub __autoexecute{
+	my $self = shift;
+	
+	$log->debug("__autoexecute()");
+	
+	# we shall have somewhere to callback to
+	my $funcPtr = $self->autoExecuteCallback() || return;
+	
+	return unless ref($funcPtr) eq 'CODE';
+	
+	# we shall have a connection id to send as param
+	my $cnxid = $self->connectionID() || return;
+	
+	# execute ourself after some cleanup
+	$self->cleanResults;
+	$self->execute();
+	
+	# execute the callback
+	eval { &{$funcPtr}($self, $cnxid) };
+
+	# oops, failed
+	if ($@) {
+		my $funcName = Slim::Utils::PerlRunTime::realNameForCodeRef($funcPtr);
+		logError("While trying to run function coderef [$funcName]: [$@] => deleting subscription");
+
+		my $name = $self->getRequestString();
+		my $clientid = $self->clientid() || 'global';
+		delete $subscribers{$cnxid}{$name}{$clientid};
+		Slim::Utils::Timers::killTimers($self, \&__autoexecute);
+	}
+
+}
+
 =head1 SEE ALSO
 
 =cut

Modified: trunk/server/Slim/Plugin/CLI/Plugin.pm
URL: http://svn.slimdevices.com/trunk/server/Slim/Plugin/CLI/Plugin.pm?rev=11404&r1=11403&r2=11404&view=diff
==============================================================================
--- trunk/server/Slim/Plugin/CLI/Plugin.pm (original)
+++ trunk/server/Slim/Plugin/CLI/Plugin.pm Sun Feb 11 16:52:54 2007
@@ -277,6 +277,7 @@
 	
 	close $client_socket;
 	delete($connections{$client_socket});
+	Slim::Control::Request::unregisterAutoExecute($client_socket);
 	
 	$log->info("Closed connection with $client_id (" . (keys %connections) . " active connections)");
 }
@@ -527,7 +528,9 @@
 
 	# remember we're the source and the $client_socket
 	$request->source('CLI');
-	$request->privateData($client_socket);
+	$request->connectionID($client_socket);
+	# set this in case the query can be subscribed to
+	$request->autoExecuteCallback(\&cli_request_write);
 	
 	my $cmd = $request->getRequest(0);
 	
@@ -550,7 +553,7 @@
 			if (defined $client) {
 				$log->info("Request [$cmd] requires client, allocated $clientid");
 			} else {
-				$log->info("Request [$cmd] requires client, none found!");
+				$log->warn("Request [$cmd] requires client, none found!");
 			}
 		}
 	}
@@ -619,7 +622,7 @@
 		
 		else {
 
-			$log->info("Request [$cmd] unkown or missing client -- will echo as is...");
+			$log->warn("Request [$cmd] unkown or missing client -- will echo as is...");
 		}
 	}
 		
@@ -635,7 +638,7 @@
 
 	$log->debug($request->getRequestString);
 
-	$client_socket = $request->privateData() unless defined $client_socket;
+	$client_socket = $request->connectionID() unless defined $client_socket;
 
 	my $encoding   = $request->getParam('charset') || 'utf8';
 	my @elements   = $request->renderAsArray($encoding);
@@ -770,7 +773,7 @@
 	}
 
 	my $param = $request->getParam('_newvalue');
-	my $client_socket = $request->privateData();
+	my $client_socket = $request->connectionID();
 	
 	if (!defined $client_socket) {
 		$request->setStatusBadParams();
@@ -802,7 +805,7 @@
 		return;
 	}
 
-	my $client_socket = $request->privateData();
+	my $client_socket = $request->connectionID();
 	
 	if (!defined $client_socket) {
 		$request->setStatusBadParams();
@@ -826,7 +829,7 @@
 	}
 
 	my $param = $request->getParam('_functions');
-	my $client_socket = $request->privateData();
+	my $client_socket = $request->connectionID();
 	
 	if (!defined $client_socket) {
 		$request->setStatusBadParams();
@@ -980,7 +983,7 @@
 
 			# don't echo twice to the sender
 			if (!($request->source() eq 'CLI' && 
-				  $request->privateData() eq $client_socket)) {
+				  $request->connectionID() eq $client_socket)) {
 
 				# assume no array in {'listen'}: we send everything
 				$sent = 1;
@@ -1057,7 +1060,7 @@
 	$request->cleanResults();
 	$request->execute();
 	
-	my $client_socket = $request->privateData();
+	my $client_socket = $request->connectionID();
 
 	cli_request_write($request);
 



More information about the checkins mailing list