commit ddd2e62c13cc8e2dfca84bf814dbdc8f2b0a5c97 Author: Ben Roberts Date: Sun Oct 10 11:36:58 2010 +0100 Imports code from RippingCluster project Imports all generic classes from the RippingCluster project, with any ripping-specific code stripped out. Tested against a dummy project for errors. diff --git a/.buildpath b/.buildpath new file mode 100644 index 0000000..c81e4ab --- /dev/null +++ b/.buildpath @@ -0,0 +1,5 @@ + + + + + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9a96648 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +# Eclipse metadata +/.buildpath +/.project +/.settings/ + diff --git a/.project b/.project new file mode 100644 index 0000000..b8d9c6c --- /dev/null +++ b/.project @@ -0,0 +1,22 @@ + + + sihnon-php-lib + + + + + + org.eclipse.wst.validation.validationbuilder + + + + + org.eclipse.dltk.core.scriptbuilder + + + + + + org.eclipse.php.core.PHPNature + + diff --git a/.settings/org.eclipse.php.core.prefs b/.settings/org.eclipse.php.core.prefs new file mode 100644 index 0000000..491c3e0 --- /dev/null +++ b/.settings/org.eclipse.php.core.prefs @@ -0,0 +1,7 @@ +#Sun Oct 10 10:59:03 BST 2010 +eclipse.preferences.version=1 +include_path=0;/sihnon-php-lib/source +org.eclipse.php.core.phpForamtterIndentationSize=4 +org.eclipse.php.core.phpForamtterUseTabs=false +phpVersion=php5.3 +use_asp_tags_as_php=false diff --git a/.settings/org.eclipse.php.ui.prefs b/.settings/org.eclipse.php.ui.prefs new file mode 100644 index 0000000..048ef5a --- /dev/null +++ b/.settings/org.eclipse.php.ui.prefs @@ -0,0 +1,3 @@ +#Sun Oct 10 10:59:06 BST 2010 +eclipse.preferences.version=1 +org.eclipse.php.ui.text.custom_code_templates= diff --git a/private/config.php.dist b/private/config.php.dist new file mode 100644 index 0000000..45705ec --- /dev/null +++ b/private/config.php.dist @@ -0,0 +1,32 @@ + \ No newline at end of file diff --git a/private/dbconfig.conf.dist b/private/dbconfig.conf.dist new file mode 100644 index 0000000..7ab2f53 --- /dev/null +++ b/private/dbconfig.conf.dist @@ -0,0 +1,5 @@ +hostname = localhost +username = username +password = password +dbname = database + diff --git a/source/lib/Sihnon/BackgroundTask.class.php b/source/lib/Sihnon/BackgroundTask.class.php new file mode 100644 index 0000000..28d6148 --- /dev/null +++ b/source/lib/Sihnon/BackgroundTask.class.php @@ -0,0 +1,17 @@ + \ No newline at end of file diff --git a/source/lib/Sihnon/Cache.class.php b/source/lib/Sihnon/Cache.class.php new file mode 100644 index 0000000..291823c --- /dev/null +++ b/source/lib/Sihnon/Cache.class.php @@ -0,0 +1,62 @@ +config = $config; + $this->cache_dir = $config->get('cache.base_dir'); + + if (is_dir($this->cache_dir)) { + if ( ! is_writeable($this->cache_dir)) { + throw new Sihnon_Exception_InvalidCacheDir(); + } + } else { + if ( ! Sihnon_Main::mkdir_recursive($this->cache_dir)) { + throw new Sihnon_Exception_InvalidCacheDir(); + } + } + } + + protected function cacheFilename($source_filename) { + return $this->cache_dir . sha1($source_filename); + } + + public function exists($source_filename, $ttl = 3600) { + $cache_filename = $this->cacheFilename($source_filename); + + // Check to see if the file is cached + if (!file_exists($cache_filename)) { + return false; + } + + // Check to see if the cache has expired + if (filemtime($cache_filename) + $ttl < time()) { + // Delete the cached item + unlink($cache_filename); + return false; + } + + return true; + } + + public function store($source_filename, $content) { + $cache_filename = $this->cacheFilename($source_filename); + return file_put_contents($cache_filename, $content); + } + + public function fetch($source_filename, $ttl = 3600) { + $cache_filename = $this->cacheFilename($source_filename); + + if (!$this->exists($source_filename)) { + throw new Sihnon_Exception_CacheObjectNotFound($source_filename); + } + + return file_get_contents($cache_filename); + } + +}; + +?> \ No newline at end of file diff --git a/source/lib/Sihnon/Config.class.php b/source/lib/Sihnon/Config.class.php new file mode 100644 index 0000000..bea4c86 --- /dev/null +++ b/source/lib/Sihnon/Config.class.php @@ -0,0 +1,145 @@ +string) + */ + private $databaseConfig = array(); + + /** + * Associative array of settings loaded from the database + * @var array(string=>array(string=>string)) + */ + private $settings = array(); + + /** + * Constructs a new instance of the Config class + * + * @param string $dbconfig Database configuration file contents + * @return Sihnon_Config + */ + public function __construct($dbconfig) { + $this->dbconfig = $dbconfig; + + $this->parseDatabaseConfig(); + } + + /** + * Parses the contents of the database configuration file so that individual settings can be retrieved. + * + */ + public function parseDatabaseConfig() { + $this->databaseConfig = parse_ini_file($this->dbconfig); + } + + /** + * Returns the value of the named item from the database configuration file + * + * @param string $key Name of the setting to retrieve + */ + public function getDatabase($key) { + if (!isset($this->databaseConfig[$key])) { + throw new Sihnon_Exception_DatabaseConfigMissing($key); + } + + return $this->databaseConfig[$key]; + } + + /** + * Sets the database instance used by this object + * + * @param Sihnon_Database $database Database instance + */ + public function setDatabase(Sihnon_Database $database) { + $this->database = $database; + $this->preload(); + } + + /** + * Loads the entire list of settings from the database + * + */ + private function preload() { + if (!$this->database) { + throw new Sihnon_Exception_NoDatabaseConnection(); + } + + $this->settings = $this->database->selectAssoc('SELECT name,type,value FROM settings', 'name', array('name', 'value', 'type')); + } + + /** + * Identifies whether the named setting exists + * + * @param string $key Name of the setting + * @return bool + */ + public function exists($key) { + return isset($this->settings[$key]); + } + + /** + * Fetches the value of the named setting + * + * @param string $key Name of the setting + */ + public function get($key) { + if (!isset($this->settings[$key])) { + throw new Sihnon_Exception_UnknownSetting($key); + } + + switch ($this->settings[$key]['type']) { + case self::TYPE_STRING_LIST: + return array_map('trim', explode("\n", $this->settings[$key]['value'])); + + default: + return $this->settings[$key]['value']; + } + } + +}; + +?> diff --git a/source/lib/Sihnon/Database.class.php b/source/lib/Sihnon/Database.class.php new file mode 100644 index 0000000..6582273 --- /dev/null +++ b/source/lib/Sihnon/Database.class.php @@ -0,0 +1,141 @@ +config = $config; + + $this->hostname = $this->config->getDatabase('hostname'); + $this->username = $this->config->getDatabase('username'); + $this->password = $this->config->getDatabase('password'); + $this->dbname = $this->config->getDatabase('dbname'); + + try { + $this->dbh = new PDO("mysql:host={$this->hostname};dbname={$this->dbname}", $this->username, $this->password); + } catch (PDOException $e) { + throw new Sihnon_Exception_DatabaseConnectFailed($e->getMessage()); + } + + } + + public function __destruct() { + $this->dbh = null; + } + + public function selectAssoc($sql, $key_col, $value_cols) { + $results = array(); + + foreach ($this->dbh->query($sql) as $row) { + if (is_array($value_cols)) { + $values = array(); + foreach ($value_cols as $value_col) { + $values[$value_col] = $row[$value_col]; + } + + $results[$row[$key_col]] = $values; + } else { + $results[$row[$key_col]] = $row[$value_col]; + } + } + + return $results; + } + + public function selectList($sql, $bind_params = null) { + if ($bind_params) { + $stmt = $this->dbh->prepare($sql); + + foreach ($bind_params as $param) { + $stmt->bindValue(':'.$param['name'], $param['value'], $param['type']); + } + + $result = $stmt->execute(); + if (!$result) { + list($dummy, $code, $message) = $stmt->errorInfo(); + throw new Sihnon_Exception_DatabaseQueryFailed($message, $code); + } + + return $stmt->fetchAll(); + + } else { + $results = array(); + + $result = $this->dbh->query($sql); + foreach ($result as $row) { + $results[] = $row; + } + + return $results; + } + } + + public function selectOne($sql, $bind_params = null) { + $rows = $this->selectList($sql, $bind_params); + if (count($rows) != 1) { + throw new Sihnon_Exception_ResultCountMismatch(count($rows)); + } + + return $rows[0]; + } + + public function insert($sql, $bind_params = null) { + $stmt = $this->dbh->prepare($sql); + + if ($bind_params) { + foreach ($bind_params as $param) { + if (isset($param['type'])) { + $stmt->bindValue(':'.$param['name'], $param['value'], $param['type']); + } else { + $stmt->bindValue(':'.$param['name'], $param['value']); + } + } + } + + $result = $stmt->execute(); + if (!$result) { + list($code, $dummy, $message) = $stmt->errorInfo(); + throw new Sihnon_Exception_DatabaseQueryFailed($message, $code); + } + } + + public function update($sql, $bind_params = null) { + $stmt = $this->dbh->prepare($sql); + + if ($bind_params) { + foreach ($bind_params as $param) { + if (isset($param['type'])) { + $stmt->bindValue(':'.$param['name'], $param['value'], $param['type']); + } else { + $stmt->bindValue(':'.$param['name'], $param['value']); + } + } + } + + $result = $stmt->execute(); + if (!$result) { + list($code, $dummy, $message) = $stmt->errorInfo(); + throw new Sihnon_Exception_DatabaseQueryFailed($message, $code); + } + } + + public function errorInfo() { + return $this->dbh->errorInfo(); + } + + public function lastInsertId() { + return $this->dbh->lastInsertId(); + } + +} + +?> diff --git a/source/lib/Sihnon/Exceptions.class.php b/source/lib/Sihnon/Exceptions.class.php new file mode 100644 index 0000000..1b6e318 --- /dev/null +++ b/source/lib/Sihnon/Exceptions.class.php @@ -0,0 +1,21 @@ + diff --git a/source/lib/Sihnon/ForegroundTask.class.php b/source/lib/Sihnon/ForegroundTask.class.php new file mode 100644 index 0000000..60adea4 --- /dev/null +++ b/source/lib/Sihnon/ForegroundTask.class.php @@ -0,0 +1,113 @@ + array('pipe', 'r'), + self::PIPE_STDOUT => array('pipe', 'w'), + self::PIPE_STDERR => array('pipe', 'w'), + ); + + $pipes = array(); + $process = proc_open($command, $descriptors, $pipes); + + stream_set_blocking($pipes[self::PIPE_STDIN], 0); // Make stdin/stdout/stderr non-blocking + stream_set_blocking($pipes[self::PIPE_STDOUT], 0); + stream_set_blocking($pipes[self::PIPE_STDERR], 0); + + if ($txLen == 0) { + fclose($pipes[0]); + } + + while (true) { + $rx = array(); // The program's stdout/stderr + if (!$stdoutDone) { + $rx[] = $pipes[self::PIPE_STDOUT]; + } + if (!$stderrDone) { + $rx[] = $pipes[self::PIPE_STDERR]; + } + + $tx = array(); // The program's stdin + if ($txOff < $txLen) { + $tx[] = $pipes[self::PIPE_STDIN]; + } + + $ex = array(); + + stream_select($rx, $tx, $ex, null, null); // Block til r/w possible + if (!empty($tx)) { + $txRet = fwrite($pipes[self::PIPE_STDIN], substr($stdin, $txOff, 8192)); + if ($txRet !== false) { + $txOff += $txRet; + } + if ($txOff >= $txLen) { + fclose($pipes[self::PIPE_STDIN]); + } + } + + foreach ($rx as $r) { + if ($r == $pipes[self::PIPE_STDOUT]) { + $chunk = fread($pipes[self::PIPE_STDOUT], 8192); + if (feof($pipes[self::PIPE_STDOUT])) { + fclose($pipes[self::PIPE_STDOUT]); $stdoutDone = true; + } + + if ($callback_stdout) { + call_user_func($callback_stdout, $identifier, $chunk); + } else { + $stdout .= $chunk; + } + + } else if ($r == $pipes[self::PIPE_STDERR]) { + $chunk = fread($pipes[self::PIPE_STDERR], 8192); + if (feof($pipes[self::PIPE_STDERR])) { + fclose($pipes[self::PIPE_STDERR]); $stderrDone = true; + } + + if ($callback_stderr) { + call_user_func($callback_stderr, $identifier, $chunk); + } else { + $stderr .= $chunk; + } + } + } + + if (!is_resource($process)) + break; + + if ($txOff >= $txLen && $stdoutDone && $stderrDone) + break; + } + + return array(proc_close($process), $stdout, $stderr); + } + +} + diff --git a/source/lib/Sihnon/IPlugin.class.php b/source/lib/Sihnon/IPlugin.class.php new file mode 100644 index 0000000..73bdf5f --- /dev/null +++ b/source/lib/Sihnon/IPlugin.class.php @@ -0,0 +1,11 @@ + \ No newline at end of file diff --git a/source/lib/Sihnon/IPluginFactory.class.php b/source/lib/Sihnon/IPluginFactory.class.php new file mode 100644 index 0000000..eee4068 --- /dev/null +++ b/source/lib/Sihnon/IPluginFactory.class.php @@ -0,0 +1,9 @@ + \ No newline at end of file diff --git a/source/lib/Sihnon/Log.class.php b/source/lib/Sihnon/Log.class.php new file mode 100644 index 0000000..781dd65 --- /dev/null +++ b/source/lib/Sihnon/Log.class.php @@ -0,0 +1,61 @@ +database = $database; + $this->config = $config; + $this->table = $table; + + } + + public function log($severity, $message) { + $this->database->insert("INSERT INTO {$this->table} (level,ctime,pid,hostname,progname,line,message) VALUES(:level, :ctime, :pid, :hostname, :progname, :line, :message)", + array( + array('name' => 'level', 'value' => $severity, 'type' => PDO::PARAM_STR), + array('name' => 'ctime', 'value' => time(), 'type' => PDO::PARAM_INT), + array('name' => 'pid', 'value' => 0, 'type' => PDO::PARAM_INT), + array('name' => 'hostname', 'value' => self::$hostname, 'type' => PDO::PARAM_STR), + array('name' => 'progname', 'value' => 'webui', 'type' => PDO::PARAM_STR), + array('name' => 'line', 'value' => 0, 'type' => PDO::PARAM_INT), + array('name' => 'message', 'value' => $message, 'type' => PDO::PARAM_STR) + ) + ); + } + + public function debug($message) { + return $this->log(self::LEVEL_DEBUG, $message); + } + + public function info($message) { + return $this->log(self::LEVEL_INFO, $message); + } + + public function warning($message) { + return $this->log(self::LEVEL_WARNING, $message); + } + + public function error($message) { + return $this->log(self::LEVEL_ERROR, $message); + } + + public static function initialise() { + self::$hostname = trim(`hostname`); + } + +} + +Sihnon_Log::initialise(); + +?> diff --git a/source/lib/Sihnon/LogEntry.class.php b/source/lib/Sihnon/LogEntry.class.php new file mode 100644 index 0000000..0d3916c --- /dev/null +++ b/source/lib/Sihnon/LogEntry.class.php @@ -0,0 +1,97 @@ +id = $id; + $this->level = $level; + $this->ctime = $ctime; + $this->pid = $pid; + $this->hostname = $hostname; + $this->progname = $progname; + $this->line = $line; + $this->message = $message; + } + + public static function fromDatabaseRow($row) { + return new Sihnon_ClientLogEntry( + $row['id'], + $row['level'], + $row['ctime'], + $row['pid'], + $row['hostname'], + $row['progname'], + $row['line'], + $row['message'] + ); + } + + public static function fromId($id) { + $database = Sihnon_Main::instance()->database(); + return Sihnon_ClientLogEntry::fromDatabaseRow( + $database->selectOne('SELECT * FROM '.self::$table_name.' WHERE id=:id', array( + array('name' => 'id', 'value' => $id, 'type' => PDO::PARAM_INT) + ) + ) + ); + } + + public static function recent($limit = 100) { + $entries = array(); + + $database = Sihnon_Main::instance()->database(); + foreach ($database->selectList('SELECT * FROM '.self::$table_name.' ORDER BY ctime DESC LIMIT :limit', array( + array('name' => 'limit', 'value' => $limit, 'type' => PDO::PARAM_INT) + )) as $row) { + $entries[] = self::fromDatabaseRow($row); + } + + return $entries; + } + + public function id() { + return $this->id; + } + + public function level() { + return $this->level; + } + + public function ctime() { + return $this->ctime; + } + + public function pid() { + return $this->pid; + } + + public function hostname() { + return $this->hostname; + } + + public function progname() { + return $this->progname; + } + + public function line() { + return $this->line; + } + + public function message() { + return $this->message; + } + +}; + +?> diff --git a/source/lib/Sihnon/Main.class.php b/source/lib/Sihnon/Main.class.php new file mode 100644 index 0000000..3a98ebe --- /dev/null +++ b/source/lib/Sihnon/Main.class.php @@ -0,0 +1,172 @@ +config = new Sihnon_Config(Sihnon_DBConfig); + $this->database = new Sihnon_Database($this->config); + $this->config->setDatabase($this->database); + + $this->log = new Sihnon_Log($this->database, $this->config, Sihnon_Log_Table); + $this->cache = new Sihnon_Cache($this->config); + + $this->base_uri = dirname($_SERVER['SCRIPT_NAME']) . '/'; + } + + /** + * + * @return Sihnon_Main + */ + public static function instance() { + if (!self::$instance) { + self::$instance = new Sihnon_Main(); + } + + return self::$instance; + } + + /** + * + * @return Sihnon_Config + */ + public function config() { + return $this->config; + } + + /** + * + * @return Sihnon_Database + */ + public function database() { + return $this->database; + } + + /** + * + * @return Sihnon_Log + */ + public function log() { + return $this->log; + } + + /** + * + * @return Sihnon_Cache + */ + public function cache() { + return $this->cache; + } + + public function baseUri() { + return $this->base_uri; + } + + public function absoluteUrl($relative_url) { + $secure = isset($_SERVER['secure']); + $port = $_SERVER['SERVER_PORT']; + return 'http' . ($secure ? 's' : '') . '://' + . $_SERVER['HTTP_HOST'] . (($port == 80 || ($secure && $port == 443)) ? '' : ':' . $port) + . '/' . $this->base_uri . $relative_url; + } + + public static function initialise() { + spl_autoload_register(array('Sihnon_Main','autoload')); + } + + public static function autoload($classname) { + // Ensure the classname contains only valid class name characters + if (!preg_match('/^[A-Z][a-zA-Z0-9_]*$/', $classname)) { + throw new Exception('Illegal characters in classname'); // TODO Subclass this exception + } + + // Ensure the class to load begins with our prefix + if (!preg_match('/^Sihnon_/', $classname)) { + return; + } + + // Special case: All exceptions are stored in the same file + if (preg_match('/^Sihnon_Exception/', $classname)) { + require_once(Sihnon_Lib . 'Sihnon/Exceptions.class.php'); + return; + } + + // Replace any underscores with directory separators + $filename = Sihnon_Lib . preg_replace('/_/', '/', $classname); + + // Tack on the class file suffix + $filename .= '.class.php'; + + // If this file exists, load it + if (file_exists($filename)) { + require_once $filename; + } + } + + public static function mkdir_recursive($directory, $permissions=0777) { + $parts = explode('/', $directory); + $path = ''; + for ($i=1,$l=count($parts); $i<=$l; $i++) { + $iPath = $parts; + $path = join('/', array_slice($iPath, 0, $i)); + if (empty($path)) continue; + if (!file_exists($path)) { + if (!mkdir($path)) return false; + if (!chmod($path, $permissions)) return false; + } + } + return true; + } + + public static function issetelse($var, $default = null) { + if (isset($var)) { + return $var; + } + + if (is_string($default) && preg_match('/^Sihnon_Exception/', $default) && class_exists($default) && is_subclass_of($default, Sihnon_Exception)) { + throw new $default(); + } + + return $default; + } + + public static function formatDuration($time) { + if (is_null($time)) { + return 'unknown'; + } + + $labels = array('seconds', 'minutes', 'hours', 'days', 'weeks', 'months', 'years'); + $limits = array(1, 60, 3600, 86400, 604800, 2592000, 31556926, PHP_INT_MAX); + + $working_time = $time; + + $result = ""; + $ptr = count($labels) - 1; + + while ($ptr >= 0 && $working_time < $limits[$ptr]) { + --$ptr; + } + + while ($ptr >= 0) { + $unit_time = floor($working_time / $limits[$ptr]); + $working_time -= $unit_time * $limits[$ptr]; + $result = $result . ' ' . $unit_time . ' ' . $labels[$ptr]; + --$ptr; + } + + return $result; + } + +} + +Sihnon_Main::initialise(); + +?> diff --git a/source/lib/Sihnon/PluginBase.class.php b/source/lib/Sihnon/PluginBase.class.php new file mode 100644 index 0000000..360e38f --- /dev/null +++ b/source/lib/Sihnon/PluginBase.class.php @@ -0,0 +1,30 @@ + \ No newline at end of file diff --git a/source/lib/Sihnon/PluginFactory.class.php b/source/lib/Sihnon/PluginFactory.class.php new file mode 100644 index 0000000..6f623ef --- /dev/null +++ b/source/lib/Sihnon/PluginFactory.class.php @@ -0,0 +1,67 @@ +getFilename()); + $plugins[] = $plugin; + } + + return $plugins; + } + + protected static function loadPlugins($plugins, $prefix, $interface) { + self::$validPlugins[get_called_class()] = array(); + + foreach ($plugins as $plugin) { + $fullClassname = $prefix . $plugin; + if ( ! class_exists($fullClassname, true)) { + continue; + } + + if ( ! in_array($interface, class_implements($fullClassname))) { + continue; + } + + // Initialise the plugin + call_user_func(array($fullClassname, 'init')); + + self::$validPlugins[get_called_class()][$plugin] = $fullClassname; + } + } + + public static function classname($plugin) { + static::ensureScanned(); + + if ( ! self::isValidPlugin($plugin)) { + throw new Sihnon_Exception_InvalidPluginName($plugin); + } + + return self::$validPlugins[get_called_class()][$plugin]; + } + +} + +?> \ No newline at end of file diff --git a/source/lib/Sihnon/Utility/ClassFilesIterator.class.php b/source/lib/Sihnon/Utility/ClassFilesIterator.class.php new file mode 100644 index 0000000..ba4941b --- /dev/null +++ b/source/lib/Sihnon/Utility/ClassFilesIterator.class.php @@ -0,0 +1,9 @@ +current()->getFilename()); + } +} + +?> \ No newline at end of file diff --git a/source/lib/Sihnon/Utility/VisibleFilesIterator.class.php b/source/lib/Sihnon/Utility/VisibleFilesIterator.class.php new file mode 100644 index 0000000..88ec65e --- /dev/null +++ b/source/lib/Sihnon/Utility/VisibleFilesIterator.class.php @@ -0,0 +1,9 @@ +current()->getFilename(), 0, 1) == '.'); + } +} + +?> \ No newline at end of file diff --git a/source/lib/Sihnon/Utility/VisibleFilesRecursiveIterator.class.php b/source/lib/Sihnon/Utility/VisibleFilesRecursiveIterator.class.php new file mode 100644 index 0000000..6a5c65b --- /dev/null +++ b/source/lib/Sihnon/Utility/VisibleFilesRecursiveIterator.class.php @@ -0,0 +1,9 @@ +current()->getFilename(), 0, 1) == '.'); + } +} + +?> \ No newline at end of file