greyhound.php
author Dan
Tue, 26 May 2009 15:25:30 -0400
changeset 69 73780a159e15
parent 64 ee64bb096f56
child 70 efabb54a418d
permissions -rwxr-xr-x
DCOP: added safeguards for mysterious zombie behavior

#!/usr/bin/env php
<?php

/**
 * Greyhound - real web management for Amarok
 * Copyright (C) 2008 Dan Fuhry
 *
 * This program is Free Software; you can redistribute and/or modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
 */

define('GREY_VERSION', '0.1a4');

// Try to trap termination signals to cleanly close the socket when needed
// AmaroK sends a SIGTERM when it is shut down or the user requests to stop
// the script
if ( function_exists('pcntl_signal') )
{
  // required for signal handling to work
  declare(ticks=1);
  
  // trap SIGTERM
  pcntl_signal(SIGTERM, 'sigterm');
  pcntl_signal(SIGINT,  'sigterm');
  pcntl_signal(SIGUSR1, 'handle_refresh_signal');
}

@ini_set('display_errors', 'on');

// get the root
define('GREY_ROOT', dirname(__FILE__));

// what kind of terminal do we have?
$use_colors = ( @in_array(@$_SERVER['TERM'], array('linux', 'xterm', 'vt100', 'screen')) ) ? true : false;
require(GREY_ROOT . '/functions.php');

// start up...

status('Starting Greyhound Web Control v' . GREY_VERSION);
status('loading files');

require_once(GREY_ROOT . '/webserver.php');
define('SMARTY_DIR', GREY_ROOT . '/smarty/');
require_once(GREY_ROOT . '/smarty/Smarty.class.php');
require_once(GREY_ROOT . '/playlist.php');
require_once(GREY_ROOT . '/json.php');
require_once(GREY_ROOT . '/ajax.php');
require_once(GREY_ROOT . '/uiconfig.php');
require_once(GREY_ROOT . '/imagetools.php');
require_once(GREY_ROOT . '/sessions.php');

//
// LOAD OUR CONFIG
// Amarok launches Greyhound with a different wd than Greyhound's root. This means
// that we can drop our own config (set up from the web UI) in there and load that
// instead of the default config, which comes with greyhound.
//

grey_reload_config();

// create directories
@mkdir('./compiled');

// signal handler
function sigterm($signal)
{
  global $httpd, $avahi_process;
  if ( !defined('HTTPD_WS_CHILD') )
  {
    status("Caught SIGTERM, cleaning up.");
    if ( is_resource($avahi_process) )
    {
      @proc_terminate($avahi_process);
    }
  }
  
  exit(0);
}

status('doing PHP capabilities check');

if ( version_compare(PHP_VERSION, '5.1.0', '<') )
{
  burnout('The minimum required version of PHP for Greyhound is 5.1.0. PHP 5.2.x is recommended.');
}

if ( !function_exists('pcntl_signal') )
{
  warning('System does not support POSIX functions. Termination signals will result in unclean shutdown and multi-process webserving will not work.');
  $allow_fork = false;
}

if ( !function_exists('simplexml_load_file') )
{
  warning('Can\'t find support for SimpleXML, which is needed to parse the Amarok playlist file.');
  burnout('SimpleXML required to continue. You may have an outdated version of PHP; most versions of PHP 5 have SimpleXML built-in. Check your distribution\'s documentation to find out how to enable PHP\'s SimpleXML support.');
}

if ( !function_exists('imagepng') || !function_exists('imagecopyresampled') || !function_exists('imagecreatefromjpeg') || !function_exists('imagecreatefromwbmp') )
{
  warning('Can\'t find support for GD 2.0. Artwork will not be displayed. Look around in your distro\'s package manager for php-gd or php5-gd.');
}

status('initializing playlist');

// init playlist object
$playlist = array();
$amarok_home = false;
rebuild_playlist();

// startup webserver
$ip = array();
if ( !$enable_ipv4 && !$enable_ipv6 )
{
  warning('Both IPv4 and IPv6 are disabled, enabling IPv4 access');
  $enable_ipv4 = true;
}
if ( $public )
{
  if ( $enable_ipv6 )
    $ip[] = '::';
  if ( $enable_ipv4 )
    $ip[] = '0.0.0.0';
}
else
{
  if ( $enable_ipv6 )
    $ip[] = '::1';
  if ( $enable_ipv4 )
    $ip[] = '127.0.0.1';
}

$port = 7447;

try
{
  status('starting PhpHttpd');
  $httpd = new WebServer($ip, $port);
  
  // if we have avahi and proc_open support, publish the service (new)
  if ( $allowcontrol && function_exists('proc_open') && $path = which('avahi-publish') )
  {
    // get our current hostname (hack, sort of)
    $hostfile = tempnam('hostname', ( function_exists('sys_get_temp_dir') ? sys_get_temp_dir() : '/tmp' ));
    system("hostname > '$hostfile' 2>/dev/null");
    $hostname = trim(@file_get_contents($hostfile));
    unlink($hostfile);
    if ( !empty($hostname) )
    {
      status('Publishing service on local network with Avahi');
      $descriptorspec = array(
          0 => array('pipe', 'r'),
          1 => array('pipe', 'w'),
          2 => array('pipe', 'w')
        );
      $thisuser = get_current_user();
      
      $avahi_command = "'$path' -s \"{$thisuser}'s\"' AmaroK playlist on $hostname' _greyhound._tcp $port";
      $avahi_process = proc_open($avahi_command, $descriptorspec, $avahi_pipes);
      if ( !$avahi_process )
      {
        warning('proc_open() failed; could not start announcement of service on Avahi network');
      }
    }
  }
  
  // setup handlers
  status('initializing handlers');
  $httpd->add_handler('index',                'function', 'amarok_playlist');
  $httpd->add_handler('login',                'function', 'greyhound_login_page');
  $httpd->add_handler('logout',               'function', 'greyhound_logout');
  $httpd->add_handler('config',               'function', 'greyhound_config');
  $httpd->add_handler('action.json',          'function', 'ajax_request_handler');
  $httpd->add_handler('artwork',              'function', 'artwork_request_handler');
  $httpd->add_handler('api',                  'function', 'api_request_handler');
  $httpd->add_handler('scripts',              'dir',      GREY_ROOT . '/scripts');
  $httpd->add_handler('favicon.ico',          'file',     GREY_ROOT . '/amarok_icon.ico');
  $httpd->add_handler('apple-touch-icon.png', 'file',     GREY_ROOT . '/apple-touch-icon.png');
  $httpd->add_handler('spacer.gif',           'file',     GREY_ROOT . '/spacer.gif');
  $httpd->add_handler('trans80.png',          'file',     GREY_ROOT . '/trans80.png');
  $httpd->threader->ipc_register('reloadconfig', 'grey_reload_config');
  // load all themes if forking is enabled
  // Themes are loaded when the playlist is requested. This is fine for
  // single-threaded operation, but if the playlist handler is only loaded
  // in a child process, we need to preload all themes into the parent before
  // children can respond to theme resource requests.
  // if ( $allow_fork )
  // {
    status('Preloading themes');
    
    $dh = @opendir(GREY_ROOT . '/themes');
    if ( !$dh )
      burnout('Could not open themes directory');
    while ( $dir = @readdir($dh) )
    {
      if ( $dir == '.' || $dir == '..' )
        continue;
      if ( is_dir( GREY_ROOT . "/themes/$dir" ) )
        load_theme($dir);
    }
  // }
  $httpd->allow_dir_list = true;
  $httpd->allow_fork = ( $allow_fork ) ? true : false;
  $httpd->default_document = 'index';
  
  status("Entering main server loop - ^C to interrupt, listening on port $port");
  $httpd->serve();
}
catch( Exception $e )
{
  if ( strstr(strval($e), "Could not bind") || strstr(strval($e), "Address already in use") )
  {
    burnout("Could not bind to the port $ip:$port. Is Greyhound already running? Sometimes browsers don't close off their connections until Greyhound has been dead for about a minute, so try starting Greyhound again in roughly 60 seconds. If that doesn't work, type \"killall -9 php\" at a terminal and try starting Greyhound again in 60 seconds.");
  }
  burnout("Exception caught while running webserver:\n$e");
}

function handle_refresh_signal()
{
  global $httpd;
  if ( !is_object($httpd) )
    // we're not serving yet.
    return false;
    
  // we've got an httpd instance; rebuild the playlist
  rebuild_playlist();
}