functions.php
author Dan
Tue, 23 Sep 2008 23:26:18 -0400
changeset 50 1b4288399b1f
parent 34 3b817b961984
child 52 ab3541465382
permissions -rw-r--r--
Added graphical configuration, at this point only for the grey theme but others will follow soon. (This has been nearly done for two weeks or more but was on hold due to the bugs with multithreading)

<?php

/**
 * Utility functions
 * 
 * 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.
 */

/**
 * Utility/reporting functions
 */
 
/**
 * Report a fatal error and exit
 * @param string Error message
 */

function burnout($msg)
{
  global $use_colors;
  $h = @fopen('php://stderr', 'w');
  if ( $use_colors )
  {
    fwrite($h, "\x1B[31;1m[Greyhound] fatal: \x1B[37;1m");
    fwrite($h, "$msg\x1B[0m\n");
  }
  else
  {
    fwrite($h, "[Greyhound] fatal: $msg\n");
  }
  fclose($h);
  exit(1);
}

/**
 * Print a stylized status message, compatible with Linux consoles. Falls back to no control characters if on unsupported terminal.
 * @param string Status message
 */

function status($msg)
{
  global $use_colors;
  $h = @fopen('php://stderr', 'w');
  $label = ( defined('HTTPD_WS_CHILD') ) ? 'Child ' . substr(strval(getmypid()), -3) : 'Greyhound';
  if ( $use_colors )
  {
    fwrite($h, "\x1B[32;1m[$label] \x1B[32;0m$msg\x1B[0m\n");
  }
  else
  {
    fwrite($h, "[$label] $msg\n");
  }
  fclose($h);
}

/**
 * Print a stylized warning message, compatible with Linux consoles
 * @param string message message
 */

function warning($msg)
{
  global $use_colors;
  $h = @fopen('php://stderr', 'w');
  if ( $use_colors )
  {
    fwrite($h, "\x1B[33;1m[Greyhound] \x1B[0m\x1B[33mWarning:\x1B[0m $msg\n");
  }
  else
  {
    fwrite($h, "[Greyhound] Warning: $msg\n");
  }
  fclose($h);
}

/**
 * Performs an action with DCOP.
 * @param string DCOP component, e.g. player, playlist, playlistbrowser, ...
 * @param string Action to perform, e.g. stop, play, ...
 * @param string additional parameters... (NOT IMPLEMENTED)
 * @return mixed output of DCOP command
 */

function dcop_action($component, $action)
{
  $tmpfile = tempnam('amaweb', '');
  if ( !$tmpfile )
    burnout('tempnam() failed us');
  system("dcop amarok $component $action > $tmpfile");
  $output = @file_get_contents($tmpfile);
  @unlink($tmpfile);
  $output = trim($output);
  
  // detect type of output
  if ( $output == 'true' )
    return true;
  else if ( $output == 'false' )
    return false;
  else if ( preg_match('/^-?[0-9]+/', $output) )
    return intval($output);
  else
    return $output;
}

/**
 * Rebuilds the copy of the playlist in RAM
 */

$playlist_last_md5 = '';

function rebuild_playlist()
{
  // import what we need
  global $playlist, $amarok_home;
  // sync and load the playlist file
  $playlist_file = dcop_action('playlist', 'saveCurrentPlaylist');
  // do we have amarok's home?
  if ( !$amarok_home )
    $amarok_home = dirname($playlist_file);
  // check MD5 - if it's not changed, exit to save CPU cycles
  global $playlist_last_md5;
  $effective_md5 = md5_playlist_file($playlist_file);
  if ( $playlist_last_md5 == $effective_md5 )
  {
    return true;
  }
  status('Rebuilding playlist cache');
  $playlist_last_md5 = $effective_md5;
  // start XML parser
  try
  {
    $xml = simplexml_load_file($playlist_file);
  }
  catch ( Exception $e )
  {
    burnout("Caught exception trying to load playlist file:\n$e");
  }
  $attribs = $xml->attributes();
  if ( @$attribs['product'] != 'Amarok' )
  {
    burnout('Playlist is not in Amarok format');
  }
  $playlist = array();
  foreach ( $xml->children() as $child )
  {
    $attribs = $child->attributes();
    $item = array(
        'uri'    => $attribs['uri'],
        'title'  => strval($child->Title),
        'artist' => strval($child->Artist),
        'album'  => strval($child->Album),
        'length' => seconds_to_str(intval($child->Length)),
        'length_int' => intval($child->Length)
      );
    $playlist[] = $item;
  }
  // if we're a child process, signal the parent to update
  if ( defined('HTTPD_WS_CHILD') )
  {
    global $httpd;
    posix_kill($httpd->parent_pid, SIGUSR1);
  }
}

/**
 * Builds the correct MD5 check for the specified playlist XML file. This is designed to base on the list of actual tracks, disregarding
 * the rest of the text in the XML file.
 * @param string Path to playlist
 * @return string hash
 */

function md5_playlist_file($file)
{
  $contents = @file_get_contents($file);
  if ( empty($contents) )
    return false;
  $count = preg_match_all('/uniqueid="([a-fA-F0-9]+?)"/', $contents, $matches);
  $matches = implode("", $matches[1]);
  if ( empty($matches) )
  {
    // sometimes current.xml has blank unique IDs
    $count = preg_match_all('/url="([^"]+?)"/', $contents, $matches);
    $matches = implode("", $matches[1]);
  }
  return md5($matches);
}

/**
 * Converts a number to minute:second format
 * @param int Seconds
 * @return string format: mm:ss
 */

function seconds_to_str($secs)
{
  $seconds = $secs % 60;
  $minutes = ( $secs - $seconds ) / 60;
  $seconds = strval($seconds);
  $minutes = strval($minutes);
  if ( strlen($seconds) < 2 )
    $seconds = "0$seconds";
  if ( strlen($minutes) < 2 )
    $minutes = "0$minutes";
  return "$minutes:$seconds";
}

/**
 * Loads the specified theme into Smarty
 * @param string Theme ID
 * @return object Smarty object
 */

function load_theme($theme_id)
{
  global $httpd;
  static $smarty = array();
  if ( $theme_id === '__free__' )
  {
    $smarty = array();
    return false;
  }
  if ( !isset($smarty[$theme_id]) )
  {
    $smarty[$theme_id] = new Smarty();
    $smarty[$theme_id]->template_dir = GREY_ROOT . "/themes/$theme_id";
    if ( !is_dir("./compiled/$theme_id") )
      @mkdir("./compiled/$theme_id");
    $smarty[$theme_id]->compile_dir  = "./compiled/$theme_id";
    $smarty[$theme_id]->config_dir = "./config";
    $httpd->add_handler("themes/$theme_id", 'dir', GREY_ROOT . "/themes/$theme_id");
  }
  return $smarty[$theme_id];
}

/**
 * Implementation of the "which" command in native PHP.
 * @param string command
 * @return string path to executable, or false on failure
 */

function which($executable)
{
  $path = ( isset($_ENV['PATH']) ) ? $_ENV['PATH'] : ( isset($_SERVER['PATH']) ? $_SERVER['PATH'] : false );
  if ( !$path )
    // couldn't get OS's PATH
    return false;
    
  $win32 = ( PHP_OS == 'WINNT' || PHP_OS == 'WIN32' );
  $extensions = $win32 ? array('.exe', '.com', '.bat') : array('');
  $separator = $win32 ? ';' : ':';
  $paths = explode($separator, $path);
  foreach ( $paths as $dir )
  {
    foreach ( $extensions as $ext )
    {
      $fullpath = "$dir/{$executable}{$ext}";
      if ( file_exists($fullpath) && is_executable($fullpath) )
      {
        return $fullpath;
      }
    }
  }
  return false;
}

/**
 * Reload the config.
 */

function grey_reload_config()
{
  global $httpd;
  status('reloading the config');
  
  if ( file_exists('./greyhound-config.php') )
  {
    require('./greyhound-config.php');
  }
  else
  {
    // ignore this, it allows using a different config file when a Mercurial repository
    // exists in Greyhound's root directory (to allow the devs to have their own config
    // separate from the default)
    
    if ( @is_dir(GREY_ROOT . '/.hg') )
      require(GREY_ROOT . '/config.dev.php');
    else
      require(GREY_ROOT . '/config.php');
  }
}