Indefero

Indefero Git Source Tree


Root/src/IDF/Scm/Monotone/Usher.php

<?php
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
# ***** BEGIN LICENSE BLOCK *****
# This file is part of InDefero, an open source project management application.
# Copyright (C) 2010 CĂ©ondo Ltd and contributors.
#
# InDefero is free software; you can redistribute it 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.
#
# InDefero 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 more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
#
# ***** END LICENSE BLOCK ***** */

require_once(dirname(__FILE__) . "/BasicIO.php");

/**
 * Connects with the admininistrative interface of usher,
 * the monotone proxy. This class contains only static methods because
 * there is really no state to keep between each invocation, as usher
 * closes the connection after every command.
 *
 * @author Thomas Keller <me@thomaskeller.biz>
 */
class IDF_Scm_Monotone_Usher
{
    /**
     * Without giving a specific state, returns an array of all servers.
     * When a state is given, the array contains only servers which are
     * in the given state.
     *
     * @param string $state One of REMOTE, ACTIVE, WAITING, SLEEPING,
     *                      STOPPING, STOPPED, SHUTTINGDOWN or SHUTDOWN
     * @return array
     */
    public static function getServerList($state = null)
    {
        $conn = self::_triggerCommand('LIST '.$state);
        if ($conn == 'none')
            return array();

        return preg_split('/[ ]/', $conn);
    }

    /**
     * Returns an array of all open connections to the given server, or to
     * any server if no server is specified.
     * If there are no connections to list, an empty array is returned.
     *
     * Example:
     *    array("server1" => array(
     *              array("address" => "192.168.1.0", "port" => "13456"),
     *              ...
     *          ),
     *          "server2" => ...
     *    )
     *
     * @param string $server
     * @return array
     */
    public static function getConnectionList($server = null)
    {
        $conn = self::_triggerCommand('LISTCONNECTIONS '.$server);
        if ($conn == 'none')
            return array();

        $single_conns = preg_split('/[ ]/', $conn);
        $ret = array();
        foreach ($single_conns as $conn) {
            preg_match('/\((\w+)\)([^:]+):(\d+)/', $conn, $matches);
            $ret[$matches[1]][] = (object)array(
                'server'    => $matches[1],
                'address'   => $matches[2],
                'port'      => $matches[3],
            );
        }

        if ($server !== null) {
            if (array_key_exists($server, $ret))
                return $ret[$server];
            return array();
        }

        return $ret;
    }

    /**
     * Get the status of a particular server, or of the usher as a whole if
     * no server is specified.
     *
     * @param string $server
     * @return One of REMOTE, SLEEPING, STOPPING, STOPPED for servers or
     *         ACTIVE, WAITING, SHUTTINGDOWN or SHUTDOWN for usher itself
     */
    public static function getStatus($server = null)
    {
        return self::_triggerCommand('STATUS '.$server);
    }

    /**
     * Looks up the name of the server that would be used for an incoming
     * connection having the given host and pattern.
     *
     * @param string $host      Host
     * @param string $pattern   Branch pattern
     * @return server name
     * @throws IDF_Scm_Exception
     */
    public static function matchServer($host, $pattern)
    {
        $ret = self::_triggerCommand('MATCH '.$host.' '.$pattern);
        if (preg_match('/^OK: (.+)/', $ret, $m))
            return $m[1];
        preg_match('/^ERROR: (.+)/', $ret, $m);
        throw new IDF_Scm_Exception('could not match server: '.$m[1]);
    }

    /**
     * Prevent the given local server from receiving further connections,
     * and stop it once all connections are closed. The return value will
     * be the new status of that server: ACTIVE local servers will become
     * STOPPING, and WAITING and SLEEPING serveres become STOPPED.
     * Servers in other states are not affected.
     *
     * @param string $server
     * @return string State of the server after the command
     */
    public static function stopServer($server)
    {
        return self::_triggerCommand("STOP $server");
    }

    /**
     * Allow a STOPPED or STOPPING server to receive connections again.
     * The return value is the new status of that server: STOPPING servers
     * become ACTIVE, and STOPPED servers become SLEEPING. Servers in other
     * states are not affected.
     *
     * @param string $server
     * @return string State of the server after the command
     */
    public static function startServer($server)
    {
        return self::_triggerCommand('START '.$server);
    }

    /**
     * Immediately kill the given local server, dropping any open connections,
     * and prevent is from receiving new connections and restarting. The named
     * server will immediately change to state STOPPED.
     *
     * @param string $server
     * @return bool True if successful
     */
    public static function killServer($server)
    {
        return self::_triggerCommand('KILL_NOW '.$server) == 'ok';
    }

    /**
     * Do not accept new connections for any servers, local or remote.
     *
     * @return bool True if successful
     */
    public static function shutDown()
    {
        return self::_triggerCommand('SHUTDOWN') == 'ok';
    }

    /**
     * Begin accepting connections after a SHUTDOWN.
     *
     * @return bool True if successful
     */
    public static function startUp()
    {
        return self::_triggerCommand('STARTUP') == 'ok';
    }

    /**
     * Reload the config file, the same as sending SIGHUP.
     *
     * @return bool True if successful (after the configuration was reloaded)
     */
    public static function reload()
    {
        return self::_triggerCommand('RELOAD') == 'ok';
    }

    private static function _triggerCommand($cmd)
    {
        $uc = Pluf::f('mtn_usher_conf', false);
        if (!$uc || !is_readable($uc)) {
            throw new IDF_Scm_Exception(
                '"mtn_usher_conf" is not configured or not readable'
            );
        }

        $parsed_config =
            IDF_Scm_Monotone_BasicIO::parse(file_get_contents($uc));
        $host = $port = $user = $pass = null;
        foreach ($parsed_config as $stanza) {
            foreach ($stanza as $line) {
                if ($line['key'] == 'adminaddr') {
                    list($host, $port) = explode(":", @$line['values'][0]);
                    break;
                }
                if ($line['key'] == 'userpass') {
                    $user = @$line['values'][0];
                    $pass = @$line['values'][1];
                }
            }
        }

        if (empty($host)) {
            throw new IDF_Scm_Exception('usher host is empty');
        }
        if (!preg_match('/^\d+$/', $port))
        {
            throw new IDF_Scm_Exception('usher port is invalid');
        }

        if (empty($user)) {
            throw new IDF_Scm_Exception('usher user is empty');
        }

        if (empty($pass)) {
            throw new IDF_Scm_Exception('usher pass is empty');
        }

        $sock = @fsockopen($host, $port, $errno, $errstr);
        if (!$sock) {
            throw new IDF_Scm_Exception(
                "could not connect to usher: $errstr ($errno)"
            );
        }

        fwrite($sock, 'USERPASS '.$user.' '.$pass."\n");
        if (feof($sock)) {
            throw new IDF_Scm_Exception(
                'usher closed the connection - this should not happen'
            );
        }

        fwrite($sock, $cmd."\n");
        $out = '';
        while (!feof($sock)) {
            $out .= fgets($sock);
        }
        fclose($sock);
        $out = rtrim($out);

        if ($out == 'unknown command') {
            throw new IDF_Scm_Exception('unknown command: '.$cmd);
        }

        return $out;
    }
}

Archive Download this file

Page rendered in 0.09870s using 11 queries.