|
@@ -0,0 +1,229 @@
|
|
|
|
|
+#!/usr/bin/perl
|
|
|
|
|
+use strict;
|
|
|
|
|
+use warnings;
|
|
|
|
|
+use IO::Select;
|
|
|
|
|
+use IO::Socket::UNIX;
|
|
|
|
|
+use File::Temp qw( tempfile );
|
|
|
|
|
+
|
|
|
|
|
+# Prototypes
|
|
|
|
|
+sub mtime($);
|
|
|
|
|
+
|
|
|
|
|
+# User config
|
|
|
|
|
+my $STATE_TIMEOUT = 180;
|
|
|
|
|
+
|
|
|
|
|
+# App config
|
|
|
|
|
+my $SOCK_TIMEOUT = 5;
|
|
|
|
|
+my $TEMP_DIR = `getconf DARWIN_USER_TEMP_DIR`;
|
|
|
|
|
+chomp($TEMP_DIR);
|
|
|
|
|
+my $DATA_DIR = $TEMP_DIR . 'plexMonitor/';
|
|
|
|
|
+my $CMD_FILE = $DATA_DIR . 'STATE.socket';
|
|
|
|
|
+my $MAX_CMD_LEN = 4096;
|
|
|
|
|
+
|
|
|
|
|
+# Debug
|
|
|
|
|
+my $DEBUG = 0;
|
|
|
|
|
+if ($ENV{'DEBUG'}) {
|
|
|
|
|
+ $DEBUG = 1;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+# Command-line arguments
|
|
|
|
|
+my ($DELAY) = @ARGV;
|
|
|
|
|
+if (!$DELAY) {
|
|
|
|
|
+ $DELAY = 0.5;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+# Sanity check
|
|
|
|
|
+if (!-d $DATA_DIR) {
|
|
|
|
|
+ die("Bad config\n");
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+# Socket init
|
|
|
|
|
+if (-e $CMD_FILE) {
|
|
|
|
|
+ unlink($CMD_FILE);
|
|
|
|
|
+}
|
|
|
|
|
+my $sock = IO::Socket::UNIX->new(
|
|
|
|
|
+ 'Local' => $CMD_FILE,
|
|
|
|
|
+ 'Type' => SOCK_DGRAM
|
|
|
|
|
+) or die('Unable to open socket: ' . $CMD_FILE . ": ${@}\n");
|
|
|
|
|
+if (!-S $CMD_FILE) {
|
|
|
|
|
+ die('Failed to create socket: ' . $CMD_FILE . "\n");
|
|
|
|
|
+}
|
|
|
|
|
+my $select = IO::Select->new($sock)
|
|
|
|
|
+ or die('Unable to select socket: ' . $CMD_FILE . ": ${!}\n");
|
|
|
|
|
+
|
|
|
|
|
+# Subscribers
|
|
|
|
|
+my @subscribers = ();
|
|
|
|
|
+
|
|
|
|
|
+# State
|
|
|
|
|
+my $state = 'INIT';
|
|
|
|
|
+my $stateLast = $state;
|
|
|
|
|
+my $playing = 0;
|
|
|
|
|
+my $projector = 0;
|
|
|
|
|
+my $updateLast = 0;
|
|
|
|
|
+
|
|
|
|
|
+# Loop forever
|
|
|
|
|
+while (1) {
|
|
|
|
|
+
|
|
|
|
|
+ # Check for queued commands
|
|
|
|
|
+ my @ready_clients = $select->can_read($DELAY);
|
|
|
|
|
+ foreach my $fh (@ready_clients) {
|
|
|
|
|
+
|
|
|
|
|
+ # Grab the inbound text
|
|
|
|
|
+ my $path = undef();
|
|
|
|
|
+ $fh->recv($path, $MAX_CMD_LEN);
|
|
|
|
|
+ $path =~ s/^\s+//;
|
|
|
|
|
+ $path =~ s/\s+$//;
|
|
|
|
|
+ if ($DEBUG) {
|
|
|
|
|
+ print STDERR 'Got path: ' . $path . "\n";
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ # Only accept valid paths
|
|
|
|
|
+ if (!-r $path) {
|
|
|
|
|
+ print STDERR 'Invalid socket path: ' . $path . "\n";
|
|
|
|
|
+ next;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ # Open the new socket
|
|
|
|
|
+ my $sub = IO::Socket::UNIX->new(
|
|
|
|
|
+ 'Peer' => $path,
|
|
|
|
|
+ 'Type' => SOCK_DGRAM,
|
|
|
|
|
+ 'Timeout' => $SOCK_TIMEOUT
|
|
|
|
|
+ );
|
|
|
|
|
+ if (!$sub) {
|
|
|
|
|
+ print STDERR 'Unable to open socket: ' . $path . ": ${@}\n";
|
|
|
|
|
+ next;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ # Add the socket to our subscriber list
|
|
|
|
|
+ my %tmp = (
|
|
|
|
|
+ 'path' => $path,
|
|
|
|
|
+ 'socket' => $sub,
|
|
|
|
|
+ );
|
|
|
|
|
+ push(@subscribers, \%tmp);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ # Monitor the PLAY_STATUS file for changes and state
|
|
|
|
|
+ {
|
|
|
|
|
+ my $mtime = mtime($DATA_DIR . 'PLAY_STATUS');
|
|
|
|
|
+ if ($mtime > $updateLast) {
|
|
|
|
|
+ $updateLast = $mtime;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ # Grab the PLAY_STATUS value
|
|
|
|
|
+ $playing = 0;
|
|
|
|
|
+ my $fh;
|
|
|
|
|
+ open($fh, $DATA_DIR . 'PLAY_STATUS')
|
|
|
|
|
+ or die("Unable to open PLAY_STATUS\n");
|
|
|
|
|
+ my $text = <$fh>;
|
|
|
|
|
+ close($fh);
|
|
|
|
|
+ if ($text =~ /1/) {
|
|
|
|
|
+ $playing = 1;
|
|
|
|
|
+ }
|
|
|
|
|
+ if ($DEBUG) {
|
|
|
|
|
+ print STDERR 'Playing: ' . $playing . "\n";
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ # Monitor the PROJECTOR file for changes and state
|
|
|
|
|
+ {
|
|
|
|
|
+ my $mtime = mtime($DATA_DIR . 'PROJECTOR');
|
|
|
|
|
+ if ($mtime > $updateLast) {
|
|
|
|
|
+ $updateLast = $mtime;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ # Grab the PROJECTOR value
|
|
|
|
|
+ $projector = 0;
|
|
|
|
|
+ my $fh;
|
|
|
|
|
+ open($fh, $DATA_DIR . 'PROJECTOR')
|
|
|
|
|
+ or die("Unable to open PROJECTOR\n");
|
|
|
|
|
+ my $text = <$fh>;
|
|
|
|
|
+ close($fh);
|
|
|
|
|
+ if ($text =~ /1/) {
|
|
|
|
|
+ $projector = 1;
|
|
|
|
|
+ }
|
|
|
|
|
+ if ($DEBUG) {
|
|
|
|
|
+ print STDERR 'Projector: ' . $projector . "\n";
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ # Monitor the GUI, PLAYING, and MOTION files for changes only
|
|
|
|
|
+ {
|
|
|
|
|
+ my $mtime = mtime($DATA_DIR . 'PLAYING');
|
|
|
|
|
+ if ($mtime > $updateLast) {
|
|
|
|
|
+ $updateLast = $mtime;
|
|
|
|
|
+ }
|
|
|
|
|
+ $mtime = mtime($DATA_DIR . 'GUI');
|
|
|
|
|
+ if ($mtime > $updateLast) {
|
|
|
|
|
+ $updateLast = $mtime;
|
|
|
|
|
+ }
|
|
|
|
|
+ $mtime = mtime($DATA_DIR . 'MOTION');
|
|
|
|
|
+ if ($mtime > $updateLast) {
|
|
|
|
|
+ $updateLast = $mtime;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ # Calculate the new state
|
|
|
|
|
+ $stateLast = $state;
|
|
|
|
|
+ if ($projector) {
|
|
|
|
|
+
|
|
|
|
|
+ # We are always either playing or paused if the projector is on
|
|
|
|
|
+ if ($playing) {
|
|
|
|
|
+ $state = 'PLAY';
|
|
|
|
|
+ } else {
|
|
|
|
|
+ $state = 'PAUSE';
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ } else {
|
|
|
|
|
+
|
|
|
|
|
+ # If the projector is off, check the timeouts
|
|
|
|
|
+ my $timeSinceUpdate = time() - $updateLast;
|
|
|
|
|
+ if ($timeSinceUpdate > $STATE_TIMEOUT) {
|
|
|
|
|
+ $state = 'OFF';
|
|
|
|
|
+ } elsif ($timeSinceUpdate < $STATE_TIMEOUT) {
|
|
|
|
|
+ $state = 'MOTION';
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ if ($state eq 'INIT') {
|
|
|
|
|
+ $state = 'OFF';
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ # Update the state
|
|
|
|
|
+ if ($stateLast ne $state) {
|
|
|
|
|
+ if ($DEBUG) {
|
|
|
|
|
+ print STDERR 'State: ' . $stateLast . ' => ' . $state . "\n";
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ # Send notifications to all subscribers
|
|
|
|
|
+ foreach my $sub (@subscribers) {
|
|
|
|
|
+
|
|
|
|
|
+ # Drop subscribers that are not available
|
|
|
|
|
+ if (!$sub->{'socket'}->send($state)) {
|
|
|
|
|
+ print STDERR 'Unable to write state to socket: ' . $sub->{'path'} . ': ' . $state . ": ${!}\n";
|
|
|
|
|
+ print STDERR 'Dropping bad socket from subscriber list: ' . $sub->{'path'} . "\n";
|
|
|
|
|
+
|
|
|
|
|
+ my @new_subscribers = ();
|
|
|
|
|
+ foreach my $new (@subscribers) {
|
|
|
|
|
+ if ($new->{'socket'} eq $sub->{'socket'}) {
|
|
|
|
|
+ next;
|
|
|
|
|
+ }
|
|
|
|
|
+ push(@new_subscribers, $new);
|
|
|
|
|
+ }
|
|
|
|
|
+ @subscribers = @new_subscribers;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ # Save the state and value to disk
|
|
|
|
|
+ my ($fh, $tmp) = tempfile($DATA_DIR . 'STATE.XXXXXXXX', 'UNLINK' => 0);
|
|
|
|
|
+ print $fh $state . "\n";
|
|
|
|
|
+ close($fh);
|
|
|
|
|
+ rename($tmp, $DATA_DIR . 'STATE');
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+sub mtime($) {
|
|
|
|
|
+ my ($file) = @_;
|
|
|
|
|
+ my $mtime = 0;
|
|
|
|
|
+ if (-r $file) {
|
|
|
|
|
+ (undef(), undef(), undef(), undef(), undef(), undef(), undef(), undef(), undef(), $mtime, undef(), undef(), undef()) = stat($file);
|
|
|
|
|
+ }
|
|
|
|
|
+ return $mtime;
|
|
|
|
|
+}
|