43 Commits

Author SHA1 Message Date
47946bcf98 Update handbrake backend to rip to temp dir 2011-08-07 15:40:17 +01:00
d2b2dc7925 Add output filesize to jobs summary page 2011-08-06 23:11:15 +01:00
e84c1eba42 Set logging program name 2011-08-06 14:57:14 +01:00
b93efc9878 Replace if block with switch case for extensibility 2011-08-06 14:55:29 +01:00
9697654594 Propagate changes to logging system from framework library 2011-08-06 14:54:22 +01:00
5121f78cea Merge branch 'master' of git+ssh://git.sihnon.net/home/git/public/handbrake-cluster-webui 2011-08-05 19:40:42 +01:00
dbc1252bef Added config file override for worker script 2011-08-05 19:35:55 +01:00
a3e58e4ee4 Merge branch 'master' of git+ssh://git.sihnon.net/home/git/public/handbrake-cluster-webui 2011-06-28 20:11:50 +01:00
3b22b0f2c9 Bug fix: Undefined index $values[9]
RippingCluster_LogEntry overwrote base class field list, causing
undefined index notices when the base class was explicitly used.
2011-06-28 20:06:24 +01:00
efb7db35d8 Fixed typo in _inc.php 2011-06-20 22:33:18 +01:00
841a5b9f92 Revert previous untested change in include statements 2011-06-20 22:04:44 +01:00
506a6e189c Update paths for packaging 2011-06-20 19:34:27 +01:00
3da59727de Bug fix: typo in variable name 2011-06-20 19:29:11 +01:00
c0d8747b21 Update init script to background the worker process 2011-06-19 22:48:45 +01:00
e1bd324e84 Bugfix: error in client log call 2011-06-19 00:13:39 +01:00
8fe8f8ba08 Use defines for library paths 2011-06-18 23:53:25 +01:00
274bc8f3c9 Use defines for library paths 2011-06-18 22:15:36 +01:00
edc2439232 Make worker read configuration from /etc/ripping-cluster 2011-06-18 18:36:33 +01:00
bd74b0f54e Update default config files 2011-06-18 18:35:40 +01:00
cd93f13f41 Add gentoo init script 2011-06-18 18:30:03 +01:00
21bd423f02 Rename worker file 2011-06-18 17:45:20 +01:00
5adf9170c4 Merge branch 'master' of git+ssh://git.sihnon.net/home/git/public/handbrake-cluster-webui 2011-04-25 14:16:43 +01:00
ce94762905 Bug fix: undefined variable when rip progress is 0% 2011-04-25 14:05:33 +01:00
72be6d6e5f Bug fixes
1) Quoted barewords in array construction
2) Added order by clause to all() to provide consistent ordering
2011-04-25 01:52:56 +01:00
5a5c3438c1 Updated client side logging and attempt to include proper job_id 2011-04-25 01:19:18 +01:00
c0702aa98e Bug fix: typoed variable name
Caused broken javascript on rip page
2011-04-25 01:18:41 +01:00
4e58ac7135 Updated code to use new WorkerLogEntry class 2011-04-25 00:54:28 +01:00
180e63c456 Bug fixes
1) full path to run-jobs.php to ensure script is runnable
2) reordering of allWithStatus to ensure consistent results
2011-04-25 00:50:22 +01:00
9be81f92f4 Updated subclassing of Client/Worker LogEntry classes 2011-04-25 00:49:09 +01:00
a459ba283a Updated status information on home page 2011-04-25 00:48:02 +01:00
4e7a5d189a Bug fix in source deletion 2011-04-25 00:47:30 +01:00
bdfa91f9b0 Updated worker to use new logging code 2011-04-24 21:18:22 +01:00
9ec2df1f18 Bug fix: incorrect parameters to log retrieval calls 2011-04-24 11:10:07 +01:00
dbaf4968ab Updated code to use new Logging code in sihnon lib 2011-04-24 10:40:14 +01:00
b896877591 fix call to Gearman_Job::complete() 2011-04-22 00:49:32 +01:00
da263a23b7 Fixed bug with worker plugin scanning 2011-04-22 00:40:38 +01:00
edc717ef68 Bug fixes 2011-04-22 00:26:30 +01:00
d3fe08d40f Massive refactor to use SihnonFramework and PEAR's Net_Gearman 2011-04-21 23:31:21 +01:00
fa7b54b861 Add option to delete sources with support in all three backends 2010-11-19 22:24:12 +00:00
73e42a42b1 Update BackgroundTask to use same cwd for spawned process 2010-11-19 21:12:18 +00:00
7c175bb608 Merge branch 'feature-mkv-plugins'; commit 'HEAD^' 2010-10-12 19:30:27 +01:00
697002dddb Merge branch 'feature-bluray' 2010-10-12 19:26:57 +01:00
d87c924b14 Merge branch 'master', remote branch 'origin' 2010-09-24 20:10:09 +01:00
85 changed files with 808 additions and 1604 deletions

View File

@@ -0,0 +1,29 @@
#!/sbin/runscript
# Copyright 1999-2010 Gentoo Foundation
# Distributed under the terms of the GNU General Public License v2
PID_FILE="/var/run/ripping-cluster-worker.pid"
depend() {
need localmount net
use dns logger puppetmaster netmount nfsmount
}
start() {
ebegin "Starting ripping-cluster-worker"
start-stop-daemon --start --quiet \
--background --make-pidfile --pidfile ${PID_FILE} \
--exec /usr/bin/php /usr/lib/ripping-cluster/worker/ripping-cluster-worker.php
eend $? "Failed to start ripping-cluster-worker"
}
stop() {
ebegin "Stopping ripping-cluster-worker"
start-stop-daemon --stop --quiet \
--pidfile ${PID_FILE}
local ret=$?
eend ${ret} "Failed to stop puppet"
rm -f ${PID_FILE}
return ${ret}
}

View File

@@ -1,23 +0,0 @@
<?php
/**
* RippingCluster Library path
*
* Specifies the absolute or relative path to the RippingCluster library directory, relative to the webui root directory.
* Path must end with a trailing slash.
*
* @var string
*/
define('RippingCluster_Lib', '../lib/');
/**
* RippingCluster Database Configuration
*
* Specifies the absolute or relative path to the RippingCluster database configuration file.
* This is a standard ini type file, containing the config required to connect to the database.
*
* @var string
*/
define('RippingCluster_DBConfig', '../dbconfig.conf');
?>

View File

@@ -1,5 +0,0 @@
hostname = localhost
username = handbrake
password = handbrake
dbname = handbrake_cluster

View File

@@ -1,17 +0,0 @@
<?php
class RippingCluster_BackgroundTask {
protected function __construct() {
}
public static function run($command) {
$pipes = array();
$pid = proc_open($command . ' &', array(), $pipes);
proc_close($pid);
}
};
?>

View File

@@ -1,62 +0,0 @@
<?php
class RippingCluster_Cache {
protected $config;
protected $cache_dir;
public function __construct(RippingCluster_Config $config) {
$this->config = $config;
$this->cache_dir = $config->get('cache.base_dir');
if (is_dir($this->cache_dir)) {
if ( ! is_writeable($this->cache_dir)) {
throw new RippingCluster_Exception_InvalidCacheDir();
}
} else {
if ( ! RippingCluster_Main::mkdir_recursive($this->cache_dir)) {
throw new RippingCluster_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 RippingCluster_Exception_CacheObjectNotFound($source_filename);
}
return file_get_contents($cache_filename);
}
};
?>

View File

@@ -1,13 +0,0 @@
<?php
class RippingCluster_ClientLogEntry extends RippingCluster_LogEntry {
public static function initialise() {
parent::$table_name = 'client_log';
}
};
RippingCluster_ClientLogEntry::initialise();
?>

View File

@@ -1,145 +0,0 @@
<?php
class RippingCluster_Config {
/**
* Boolean value type
* @var bool
*/
const TYPE_BOOL = 'bool';
/**
* Integer value type
* @var int
*/
const TYPE_INT = 'int';
/**
* Float value type
* @var float
*/
const TYPE_FLOAT = 'float';
/**
* String value type
* @var string
*/
const TYPE_STRING = 'string';
/**
* String List value type; list of newline separated strings
* @var array(string)
*/
const TYPE_STRING_LIST = 'array(string)';
/**
* Contents of the dbconfig file
* @var string
*/
private $dbconfig;
/**
* Database object created for the lifetime of this script
* @var RippingCluster_Database
*/
private $database;
/**
* Associative array of connection parameters for the database configuration
* @var array(string=>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 RippingCluster_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 RippingCluster_Exception_DatabaseConfigMissing($key);
}
return $this->databaseConfig[$key];
}
/**
* Sets the database instance used by this object
*
* @param RippingCluster_Database $database Database instance
*/
public function setDatabase(RippingCluster_Database $database) {
$this->database = $database;
$this->preload();
}
/**
* Loads the entire list of settings from the database
*
*/
private function preload() {
if (!$this->database) {
throw new RippingCluster_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 RippingCluster_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'];
}
}
};
?>

View File

@@ -1,141 +0,0 @@
<?php
class RippingCluster_Database {
private $config;
private $dbh;
private $hostname;
private $username;
private $password;
private $dbname;
private $prepared_statements = array();
public function __construct(RippingCluster_Config $config) {
$this->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 RippingCluster_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 RippingCluster_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 RippingCluster_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 RippingCluster_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 RippingCluster_Exception_DatabaseQueryFailed($message, $code);
}
}
public function errorInfo() {
return $this->dbh->errorInfo();
}
public function lastInsertId() {
return $this->dbh->lastInsertId();
}
}
?>

View File

@@ -1,32 +0,0 @@
<?php
class RippingCluster_Exception extends Exception {};
class RippingCluster_Exception_DatabaseException extends RippingCluster_Exception {};
class RippingCluster_Exception_DatabaseConfigMissing extends RippingCluster_Exception_DatabaseException {};
class RippingCluster_Exception_DatabaseConnectFailed extends RippingCluster_Exception_DatabaseException {};
class RippingCluster_Exception_NoDatabaseConnection extends RippingCluster_Exception_DatabaseException {};
class RippingCluster_Exception_DatabaseQueryFailed extends RippingCluster_Exception_DatabaseException {};
class RippingCluster_Exception_ResultCountMismatch extends RippingCluster_Exception_DatabaseException {};
class RippingCluster_Exception_ConfigException extends RippingCluster_Exception {};
class RippingCluster_Exception_UnknownSetting extends RippingCluster_Exception_ConfigException {};
class RippingCluster_Exception_TemplateException extends RippingCluster_Exception {};
class RippingCluster_Exception_AbortEntirePage extends RippingCluster_Exception_TemplateException {};
class RippingCluster_Exception_Unauthorized extends RippingCluster_Exception_TemplateException {};
class RippingCluster_Exception_FileNotFound extends RippingCluster_Exception_TemplateException {};
class RippingCluster_Exception_InvalidParameters extends RippingCluster_Exception_TemplateException {};
class RippingCluster_Exception_InvalidSourceDirectory extends RippingCluster_Exception {};
class RippingCluster_Exception_CacheException extends RippingCluster_Exception {};
class RippingCluster_Exception_InvalidCacheDir extends RippingCluster_Exception_CacheException {};
class RippingCluster_Exception_CacheObjectNotFound extends RippingCluster_Exception_CacheException {};
class RippingCluster_Exception_LogicException extends RippingCluster_Exception {};
class RippingCluster_Exception_JobNotRunning extends RippingCluster_Exception_LogicException {};
class RippingCluster_Exception_InvalidPluginName extends RippingCluster_Exception {};
?>

View File

@@ -1,113 +0,0 @@
<?php
class RippingCluster_ForegroundTask {
const PIPE_STDIN = 0;
const PIPE_STDOUT = 1;
const PIPE_STDERR = 2;
private function __construct() {
}
/**
*
* Code largely taken from user submitted comment on http://php.sihnon.net/manual/en/function.proc-open.php
* @param unknown_type $command
* @param unknown_type $cwd
* @param unknown_type $env
* @param unknown_type $stdin
* @param unknown_type $callback_stdout
* @param unknown_type $callback_stderr
*/
public static function execute($command, $cwd = null, $env = null, $stdin = null, $callback_stdout = null, $callback_stderr = null, $identifier = null) {
$txOff = 0;
$txLen = strlen($stdin);
$stdout = '';
$stdoutDone = FALSE;
$stderr = '';
$stderrDone = FALSE;
$descriptors = array(
self::PIPE_STDIN => 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);
}
}

View File

@@ -1,11 +0,0 @@
<?php
interface RippingCluster_IPlugin {
public static function init();
public static function name();
}
?>

View File

@@ -1,9 +0,0 @@
<?php
interface RippingCluster_IPluginFactory {
public static function init();
}
?>

View File

@@ -1,61 +0,0 @@
<?php
class RippingCluster_Log {
private static $hostname = '';
private $database;
private $config;
private $table;
public function __construct(RippingCluster_Database $database, RippingCluster_Config $config, $table) {
$this->database = $database;
$this->config = $config;
$this->table = $table;
}
public function log($severity, $message, $job_id = 0) {
$this->database->insert("INSERT INTO {$this->table} (job_id,level,ctime,pid,hostname,progname,line,message) VALUES(:job_id, :level, :ctime, :pid, :hostname, :progname, :line, :message)",
array(
array('name' => 'job_id', 'value' => $job_id, 'type' => PDO::PARAM_INT),
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)
)
);
if (HBC_File == 'worker') {
echo date("r") . ' ' . $message . "\n";
}
}
public function debug($message, $job_id = 0) {
return $this->log('DEBUG', $message, $job_id);
}
public function info($message, $job_id = 0) {
return $this->log('INFO', $message, $job_id);
}
public function warning($message, $job_id = 0) {
return $this->log('WARNING', $message, $job_id);
}
public function error($message, $job_id = 0) {
return $this->log('ERROR', $message, $job_id);
}
public static function initialise() {
self::$hostname = trim(`hostname`);
}
}
RippingCluster_Log::initialise();
?>

View File

@@ -1,122 +0,0 @@
<?php
abstract class RippingCluster_LogEntry {
protected static $table_name = "";
protected $id;
protected $job_id;
protected $level;
protected $ctime;
protected $pid;
protected $hostname;
protected $progname;
protected $line;
protected $message;
protected function __construct($id, $job_id, $level, $ctime, $pid, $hostname, $progname, $line, $message) {
$this->id = $id;
$this->job_id = $job_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 RippingCluster_ClientLogEntry(
$row['id'],
$row['job_id'],
$row['level'],
$row['ctime'],
$row['pid'],
$row['hostname'],
$row['progname'],
$row['line'],
$row['message']
);
}
public static function fromId($id) {
$database = RippingCluster_Main::instance()->database();
return RippingCluster_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 = RippingCluster_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 static function recentForJob($job_id, $limit = 100) {
$entries = array();
$database = RippingCluster_Main::instance()->database();
foreach ($database->selectList('SELECT * FROM '.self::$table_name.' WHERE job_id=:job_id ORDER BY ctime DESC LIMIT :limit', array(
array('name' => 'job_id', 'value' => $job_id, 'type' => PDO::PARAM_INT),
array('name' => 'limit', 'value' => $limit, 'type' => PDO::PARAM_INT)
)) as $row) {
$entries[] = self::fromDatabaseRow($row);
}
return $entries;
}
public static function allForNoJob() {
return self::allForJob(0);
}
public function id() {
return $this->id;
}
public function jobId() {
return $this->job_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;
}
};
?>

View File

@@ -1,215 +0,0 @@
<?php
require 'smarty/Smarty.class.php';
class RippingCluster_Main {
private static $instance;
private $smarty;
private $config;
private $database;
private $log;
private $request;
private $cache;
private $base_uri;
private function __construct() {
$request_string = isset($_GET['l']) ? $_GET['l'] : '';
$log_table = null;
switch(HBC_File) {
case 'index':
case 'run-jobs': {
$log_table = 'client_log';
} break;
case 'worker': {
$log_table = 'worker_log';
}
}
$this->config = new RippingCluster_Config(RippingCluster_DBConfig);
$this->database = new RippingCluster_Database($this->config);
$this->config->setDatabase($this->database);
$this->log = new RippingCluster_Log($this->database, $this->config, $log_table);
$this->request = new RippingCluster_RequestParser($request_string);
$this->cache = new RippingCluster_Cache($this->config);
$this->smarty = new Smarty();
$this->smarty->template_dir = './templates';
$this->smarty->compile_dir = './tmp/templates';
$this->smarty->cache_dir = './tmp/cache';
$this->smarty->config_fir = './config';
$this->smarty->register_modifier('formatDuration', array('RippingCluster_Main', 'formatDuration'));
$this->smarty->assign('version', '0.1');
$this->base_uri = dirname($_SERVER['SCRIPT_NAME']) . '/';
$this->smarty->assign('base_uri', $this->base_uri);
}
/**
*
* @return RippingCluster_Main
*/
public static function instance() {
if (!self::$instance) {
self::$instance = new RippingCluster_Main();
}
return self::$instance;
}
public function smarty() {
return $this->smarty;
}
/**
*
* @return RippingCluster_Config
*/
public function config() {
return $this->config;
}
/**
*
* @return RippingCluster_Database
*/
public function database() {
return $this->database;
}
/**
*
* @return RippingCluster_Log
*/
public function log() {
return $this->log;
}
/**
*
* @return RippingCluster_RequestParser
*/
public function request() {
return $this->request;
}
/**
*
* @return RippingCluster_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('RippingCluster_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('/^RippingCluster_/', $classname)) {
return;
}
// Special case: All exceptions are stored in the same file
if (preg_match('/^RippingCluster_Exception/', $classname)) {
require_once(RippingCluster_Lib . 'RippingCluster/Exceptions.class.php');
return;
}
// Replace any underscores with directory separators
$filename = RippingCluster_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('/^RippingCluster_Exception/', $default) && class_exists($default) && is_subclass_of($default, RippingCluster_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;
}
}
RippingCluster_Main::initialise();
?>

View File

@@ -1,74 +0,0 @@
<?php
class RippingCluster_Page {
private $smarty;
private $request;
private $page;
public function __construct(Smarty $smarty, RippingCluster_RequestParser $request) {
$this->smarty = $smarty;
$this->request = $request;
$this->page = $request->page();
}
public function page() {
return $this->page;
}
public function template_filename() {
return $this->page . '.tpl';
}
public function evaluate($template_variables = array()) {
$code_filename = $this->page . '.php';
$template_filename = $this->template_filename();
try {
$this->render($template_filename, $code_filename, $template_variables);
} catch (RippingCluster_Exception_AbortEntirePage $e) {
return false;
} catch (RippingCluster_Exception_FileNotFound $e) {
$this->render('errors/404.tpl', 'errors/404.php');
} catch (RippingCluster_Exception $e) {
$this->render('errors/unhandled-exception.tpl', 'errors/unhandled-exception.php', array(
'exception' => $e,
));
}
return true;
}
protected function render($template_filename, $code_filename = null, $template_variables = array()) {
if ( ! $this->smarty->template_exists($template_filename)) {
throw new RippingCluster_Exception_FileNotFound($template_filename);
}
// Copy all the template variables into the namespace for this function,
// so that they are readily available to the template
foreach ($template_variables as $__k => $__v) {
$$__k = $__v;
}
// Include the template code file, which will do all the work for this page
$real_code_filename = 'pages/' . $code_filename;
if ($code_filename && file_exists($real_code_filename)) {
include $real_code_filename;
}
// Now execute the template itself, which will render the results of the code file
$this->smarty->assign('page_content', $this->smarty->fetch($template_filename));
}
public static function redirect($relative_url) {
$absolute_url = RippingCluster_Main::instance()->absoluteUrl($relative_url);
header("Location: $absolute_url");
throw new RippingCluster_Exception_AbortEntirePage();
}
};
?>

View File

@@ -1,30 +0,0 @@
<?php
/**
* Base class for all plugins, providing default implementations for
* standard plugin methods.
*
* @class RippingCluster_PluginBase
*/
class RippingCluster_PluginBase {
/**
* Provides a basic initialisation function that does nothing.
*
*/
public static function init() {
// Nothing to do
}
/**
* Returns the name of this plugin
*
* @return string
*/
public static function name() {
return static::PLUGIN_NAME;
}
}
?>

View File

@@ -1,67 +0,0 @@
<?php
abstract class RippingCluster_PluginFactory implements RippingCluster_IPluginFactory {
static private $validPlugins = array();
protected static function ensureScanned() {
if (! isset(self::$validPlugins[get_called_class()])) {
static::scan();
}
}
protected static function isValidPlugin($plugin) {
return isset(self::$validPlugins[get_called_class()][$plugin]);
}
public static function getValidPlugins() {
static::ensureScanned();
return array_keys(self::$validPlugins[get_called_class()]);
}
protected static function findPlugins($directory) {
$plugins = array();
$iterator = new RippingCluster_Utility_ClassFilesIterator(new RippingCluster_Utility_VisibleFilesIterator(new DirectoryIterator(RippingCluster_Lib . $directory)));
foreach ($iterator as /** @var SplFileInfo */ $file) {
$plugin = preg_replace('/.class.php$/', '', $file->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 RippingCluster_Exception_InvalidPluginName($plugin);
}
return self::$validPlugins[get_called_class()][$plugin];
}
}
?>

View File

@@ -1,97 +0,0 @@
<?php
class RippingCluster_RequestParser {
private $request_string;
private $page = array();
private $vars = array();
public function __construct($request_string) {
$this->request_string = $request_string;
$this->parse();
}
public function parse() {
if (!$this->request_string) {
$this->page = array('home');
return;
}
$components = explode('/', $this->request_string);
if (!$components) {
return;
}
// Read through the components list looking for elements matching known directories and files
// to determine which page this request is for
$base_dir = 'templates';
while (true) {
if ($components && ! $components[0]) {
// Skip over any empty components before we find a page
array_shift($components);
}
if ($components && is_dir($base_dir . '/' . $components[0])) {
$base_dir .= '/' . $components[0];
array_push($this->page, array_shift($components));
} elseif ($components && is_file($base_dir . '/' . $components[0] . '.tpl')) {
// We have found a valid page, so break the loop here,
// leaving the remaining components as key/value pairs
array_push($this->page, array_shift($components));
break;
} else {
// See if we've already seen a component and assumed it referred to a dir when a file of the same name exists
if ($this->page && is_file($base_dir . '.tpl')) {
break;
} elseif ( ! $components && is_file($base_dir . '/index.tpl')) {
// The last component in the path was a valid directory, and a directory index exists
array_push($this->page, 'index');
break;
} else {
// No valid page was found, so display an error page
$this->page = array('404');
return;
}
}
}
// The subsequent tokens are parameters for this page in key value pairs
while ($components) {
// If the url ended with a trailing slash, the last element will be null
$last_element = $components[count($components) - 1];
if ($last_element == "") {
array_pop($components);
}
$this->vars[array_shift($components)] = $components ? array_shift($components) : true;
}
}
public function page() {
return join('/', $this->page);
}
public function exists($key) {
return isset($this->vars[$key]);
}
public function get($key, $default = null) {
if (isset($this->vars[$key])) {
return $this->vars[$key];
}
if (is_string($default) && preg_match('/^RippingCluster_Exception/', $default) && class_exists($default) && is_subclass_of($default, RippingCluster_Exception)) {
throw new $default();
}
return $default;
}
public function request_string() {
return $this->request_string;
}
};
?>

View File

@@ -1,45 +0,0 @@
<?php
class RippingCluster_Worker {
protected $gearman;
public function __construct() {
$this->init();
}
private function init() {
if ($this->gearman) {
return;
}
$config = RippingCluster_Main::instance()->config();
$this->gearman = new GearmanWorker();
$this->gearman->addServers($config->get('rips.job_servers'));
// Load all the plugin classes
RippingCluster_Worker_PluginFactory::scan();
foreach (RippingCluster_Worker_PluginFactory::getValidPlugins() as $plugin) {
$workerFunctions = RippingCluster_Worker_PluginFactory::getPluginWorkerFunctions($plugin);
foreach ($workerFunctions as $function => $callback) {
$this->gearman->addFunction($function, $callback);
}
}
}
public function start() {
while($this->gearman->work()) {
if ($this->gearman->returnCode() != GEARMAN_SUCCESS) {
break;
}
}
return true;
}
}
?>

View File

@@ -1,21 +0,0 @@
<?php
interface RippingCluster_Worker_IPlugin extends RippingCluster_IPlugin {
/**
* Returns the list of functions (and names) implemented by this plugin for registration with Gearman
*
* @return array(string => callback)
*/
public static function workerFunctions();
/**
* Creates an instance of the Worker plugin, and uses it to execute a single job
*
* @param GearmanJob $job Gearman Job object, describing the work to be done
*/
public static function rip(GearmanJob $job);
}
?>

View File

@@ -1,140 +0,0 @@
<?php
class RippingCluster_Worker_Plugin_HandBrake extends RippingCluster_PluginBase implements RippingCluster_Worker_IPlugin {
const PLUGIN_NAME = 'HandBrake';
const DEINTERLACE_ALWAYS = 1;
const DEINTERLACE_SELECTIVELY = 2;
private $output;
private $gearman_job;
private $job;
private $rip_options;
private function __construct(GearmanJob $gearman_job) {
$this->output = '';
$this->gearman_job = $gearman_job;
$this->rip_options = unserialize($this->gearman_job->workload());
if ( ! $this->rip_options['id']) {
throw new RippingCluster_Exception_LogicException("Job ID must not be zero/null");
}
$this->job = RippingCluster_Job::fromId($this->rip_options['id']);
}
/**
* Returns the list of functions (and names) implemented by this plugin for registration with Gearman
*
* @return array(string => callback)
*/
public static function workerFunctions() {
return array(
'handbrake_rip' => array(__CLASS__, 'rip'),
);
}
/**
* Creates an instance of the Worker plugin, and uses it to execute a single job
*
* @param GearmanJob $job Gearman Job object, describing the work to be done
*/
public static function rip(GearmanJob $job) {
$rip = new self($job);
$rip->execute();
}
private function execute() {
$main = RippingCluster_Main::instance();
$config = $main->config();
$log = $main->log();
$handbrake_cmd_raw = array(
'-n', $config->get('rips.nice'),
$config->get('rips.handbrake_binary'),
$this->evaluateOption('input_filename', '-i'),
$this->evaluateOption('output_filename', '-o'),
$this->evaluateOption('title'),
$this->evaluateOption('format', '-f'),
$this->evaluateOption('video_codec', '-e'),
$this->evaluateOption('quantizer', '-q'),
$this->evaluateOption('video_width', '-w'),
$this->evaluateOption('video_height', '-l'),
$this->evaluateOption('deinterlace'),
$this->evaluateOption('audio_tracks', '-a'),
$this->evaluateOption('audio_codec', '-E'),
$this->evaluateOption('audio_names', '-A'),
$this->evaluateOption('subtitle_tracks', '-s'),
);
$handbrake_cmd = array($config->get('rips.nice_binary'));
foreach(new RecursiveIteratorIterator(new RecursiveArrayIterator($handbrake_cmd_raw)) as $value) {
$handbrake_cmd[] = escapeshellarg($value);
}
$handbrake_cmd = join(' ', $handbrake_cmd);
$log->debug($handbrake_cmd, $this->job->id());
// Change the status of this job to running
$log->debug("Setting status to Running", $this->job->id());
$this->job->updateStatus(RippingCluster_JobStatus::RUNNING, 0);
list($return_val, $stdout, $stderr) = RippingCluster_ForegroundTask::execute($handbrake_cmd, null, null, null, array($this, 'callbackOutput'), array($this, 'callbackOutput'), $this);
if ($return_val) {
$this->gearman_job->sendFail($return_val);
} else {
$this->job->updateStatus(RippingCluster_JobStatus::COMPLETE);
}
}
private function evaluateOption($name, $option = null) {
switch($name) {
case 'title': {
if (!$this->rip_options[$name] || (int)$this->rip_options[$name] < 0) {
return array('-L');
} else {
return array('-t', $this->rip_options[$name]);
}
} break;
case 'deinterlace': {
switch ($this->rip_options[$name]) {
case self::DEINTERLACE_ALWAYS:
return array('-d');
case self::DEINTERLACE_SELECTIVELY:
return array('-5');
default:
return array();
}
}
default:
return array(isset($option) ? $option : $name, $this->rip_options[$name]);
}
}
public function callbackOutput($rip, $data) {
$this->output .= $data;
while (count($lines = preg_split('/[\r\n]+/', $this->output, 2)) > 1) {
$line = $lines[0];
$rip->output = $lines[1];
$matches = array();
if (preg_match('/Encoding: task \d+ of \d+, (\d+\.\d+) %/', $line, $matches)) {
$status = $rip->job->currentStatus();
$status->updateRipProgress($matches[1]);
} else {
$log = RippingCluster_Main::instance()->log();
$log->debug($line, $rip->job->id());
}
}
}
}
?>

View File

@@ -1,13 +0,0 @@
<?php
class RippingCluster_WorkerLogEntry extends RippingCluster_LogEntry {
public static function initialise() {
parent::$table_name = 'worker_log';
}
};
RippingCluster_WorkerLogEntry::initialise();
?>

2
private/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
config.php
dbconfig.conf

92
private/config.php.dist Normal file
View File

@@ -0,0 +1,92 @@
<?php
/**
* Sihnon Framework Library path
*
* Specifies the absolute or relative path to the Sihnon Framework library directory, relative to the webui root directory.
* Path must end with a trailing slash.
*
* @var string
*/
define('SihnonFramework_Lib', '/usr/lib/sihnon-php-lib/source/lib/');
/**
* Sihnon Library path
*
* Specifies the absolute or relative path to the Sihnon library directory, relative to the webui root directory.
* Path must end with a trailing slash.
*
* @var string
*/
define('Sihnon_Lib', '/usr/lib/ripping-cluster/source/lib/');
/**
* RippingCluster Library path
*
* Specifies the absolute or relative path to the RippingCluster library directory, relative to the webui root directory.
* Path must end with a trailing slash.
*
* @var string
*/
define('RippingCluster_Lib', '/usr/lib/ripping-cluster/source/lib/');
/**
* Sihnon Database Support
*
* Specifies whether or not to include database support in the Framework
*
* @var bool
*/
define('Sihnon_DatabaseSupport', true);
/**
* Sihnon Database Configuration
*
* Specifies the absolute or relative path to the Sihnon database configuration file, required when Database support is enabled.
* This is a standard ini type file, containing the config required to connect to the database.
*
* @var string
*/
define('Sihnon_DBConfig', '/etc/ripping-cluster/dbconfig.conf');
/**
* Sihnon Config Plugin
*
* Specifies the plugin to use for configuration value storage.
* Options include:
* - Database (Requires Database Support to be enabled, and the Sihnon_ConfigTable option set)
* - FlatFile (Requires the Sihnon_ConfigFile option set)
*
* @var string
*/
define('Sihnon_ConfigPlugin', 'Database');
/**
* Sihnon Config Table
*
* Specifies the name of the database table thats used for storing configuration values
* when the Database Config Plugin is used.
*
* @var string
*/
define('Sihnon_ConfigTable', 'settings');
/**
* Sihnon Config File
*
* Specifies the name of the file used to store configuration values when the FlatFile Config Plugin is used
*
* @var string
*/
define('Sihnon_ConfigFile', '/etc/ripping-cluster/settings.txt');
/**
* Sihnon Development Mode
*
* Specifies whether or not the Framework should operate in debug mode
*
* @var bool
*/
define('Sihnon_Dev', false);
?>

View File

@@ -0,0 +1,5 @@
hostname = localhost
username = ripping
password = ripping
dbname = ripping_cluster

View File

@@ -0,0 +1,135 @@
<?php
class Net_Gearman_Job_HandBrake extends Net_Gearman_Job_Common implements RippingCluster_Worker_IPlugin {
const DEINTERLACE_ALWAYS = 1;
const DEINTERLACE_SELECTIVELY = 2;
private $output;
private $job;
public function __construct($conn, $handle) {
parent::__construct($conn, $handle);
$this->output = '';
}
public static function init() {
}
public static function name() {
}
public function run($args) {;
$main = RippingCluster_Main::instance();
$config = $main->config();
$log = $main->log();
$this->job = RippingCluster_Job::fromId($args['rip_options']['id']);
// Substitute a temporary output filename into the rip options
$args['temp_output_filename'] = tempnam($config->get('rips.temp_dir', '/tmp'), 'hbr_');
$handbrake_cmd_raw = array(
'-n', $config->get('rips.nice'),
$config->get('rips.handbrake_binary'),
self::evaluateOption($args['rip_options'], 'input_filename', '-i'),
self::evaluateOption($args, 'temp_output_filename', '-o'),
self::evaluateOption($args['rip_options'], 'title'),
self::evaluateOption($args['rip_options'], 'format', '-f'),
self::evaluateOption($args['rip_options'], 'video_codec', '-e'),
self::evaluateOption($args['rip_options'], 'quantizer', '-q'),
self::evaluateOption($args['rip_options'], 'video_width', '-w'),
self::evaluateOption($args['rip_options'], 'video_height', '-l'),
self::evaluateOption($args['rip_options'], 'deinterlace'),
self::evaluateOption($args['rip_options'], 'audio_tracks', '-a'),
self::evaluateOption($args['rip_options'], 'audio_codec', '-E'),
self::evaluateOption($args['rip_options'], 'audio_names', '-A'),
self::evaluateOption($args['rip_options'], 'subtitle_tracks', '-s'),
);
$handbrake_cmd = array($config->get('rips.nice_binary'));
foreach(new RecursiveIteratorIterator(new RecursiveArrayIterator($handbrake_cmd_raw)) as $value) {
$handbrake_cmd[] = escapeshellarg($value);
}
$handbrake_cmd = join(' ', $handbrake_cmd);
RippingCluster_WorkerLogEntry::debug($log, $this->job->id(), $handbrake_cmd);
// Change the status of this job to running
RippingCluster_WorkerLogEntry::debug($log, $this->job->id(), "Setting status to Running");
$this->job->updateStatus(RippingCluster_JobStatus::RUNNING, 0);
list($return_val, $stdout, $stderr) = RippingCluster_ForegroundTask::execute($handbrake_cmd, null, null, null, array($this, 'callbackOutput'), array($this, 'callbackOutput'), $this);
if ($return_val) {
// Remove any temporary output files
if (file_exists($args['temp_output_filename'])) {
unlink($args['temp_output_filename']);
}
$this->fail($return_val);
} else {
// Move the temporary output file to the desired destination
$move = rename($args['temp_output_filename'], $args['rip_options']['output_filename']);
if ($move) {
$this->job->updateStatus(RippingCluster_JobStatus::COMPLETE);
$this->complete( array(
'id' => $this->job->id()
));
} else {
RippingCluster_WorkerLogEntry::error($log, $this->job->id(), "Failed to move temporary output file to proper destination. File retained as '{$args['temp_output_filename']}'.");
$this->job->updateStatus(RippingCluster_JobStatus::FAILED);
$this->fail('-1');
}
}
}
private static function evaluateOption($options, $name, $option = null) {
switch($name) {
case 'title': {
if (!$options[$name] || (int)$options[$name] < 0) {
return array('-L');
} else {
return array('-t', $options[$name]);
}
} break;
case 'deinterlace': {
switch ($options[$name]) {
case self::DEINTERLACE_ALWAYS:
return array('-d');
case self::DEINTERLACE_SELECTIVELY:
return array('-5');
default:
return array();
}
}
default:
return array(isset($option) ? $option : $name, $options[$name]);
}
}
public function callbackOutput($rip, $data) {
$this->output .= $data;
while (count($lines = preg_split('/[\r\n]+/', $this->output, 2)) > 1) {
$line = $lines[0];
$rip->output = $lines[1];
$matches = array();
if (preg_match('/Encoding: task \d+ of \d+, (\d+\.\d+) %/', $line, $matches)) {
$status = $rip->job->currentStatus();
$status->updateRipProgress($matches[1]);
$this->status($matches[1], 100);
} else {
$log = RippingCluster_Main::instance()->log();
RippingCluster_WorkerLogEntry::debug($log, $rip->job->id(), $line);
}
}
}
}
?>

View File

@@ -0,0 +1,23 @@
<?php
class RippingCluster_ClientLogEntry extends RippingCluster_LogEntry {
public static function debug($logger, $job_id, $message) {
static::log($logger, SihnonFramework_Log::LEVEL_DEBUG, $job_id, $message, 'client');
}
public static function info($logger, $job_id, $message) {
static::log($logger, SihnonFramework_Log::LEVEL_INFO, $job_id, $message, 'client');
}
public static function warning($logger, $job_id, $message) {
static::log($logger, SihnonFramework_Log::LEVEL_WARNING, $job_id, $message, 'client');
}
public static function error($logger, $job_id, $message) {
static::log($logger, SihnonFramework_Log::LEVEL_ERROR, $job_id, $message, 'client');
}
};
?>

View File

@@ -0,0 +1,11 @@
<?php
class RippingCluster_Exception extends Exception {};
class RippingCluster_Exception_InvalidSourceDirectory extends RippingCluster_Exception {};
class RippingCluster_Exception_LogicException extends RippingCluster_Exception {};
class RippingCluster_Exception_JobNotRunning extends RippingCluster_Exception_LogicException {};
?>

View File

@@ -111,7 +111,7 @@ class RippingCluster_Job {
$jobs = array();
$database = RippingCluster_Main::instance()->database();
foreach ($database->selectList('SELECT * FROM jobs WHERE id > 0') as $row) {
foreach ($database->selectList('SELECT * FROM jobs WHERE id > 0 ORDER BY id DESC') as $row) {
$job = self::fromDatabaseRow($row);
self::$cache[$job->id] = $job;
@@ -135,7 +135,7 @@ class RippingCluster_Job {
$params[] = array('name' => 'limit', 'value' => $limit, 'type' => PDO::PARAM_INT);
}
foreach ($database->selectList("SELECT * FROM jobs WHERE id IN (SELECT job_id FROM job_status_current WHERE id > 0 AND status=:status) ORDER BY id {$limitSql}", $params) as $row) {
foreach ($database->selectList("SELECT * FROM jobs WHERE id IN (SELECT job_id FROM job_status_current WHERE id > 0 AND status=:status) ORDER BY id DESC {$limitSql}", $params) as $row) {
$jobs[] = self::fromDatabaseRow($row);
}
@@ -212,18 +212,16 @@ class RippingCluster_Job {
$database->update(
'DELETE FROM jobs WHERE id=:job_id LIMIT 1',
array(
array(name => 'job_id', value => $this->id, type => PDO::PARAM_INT),
array('name' => 'job_id', 'value' => $this->id, 'type' => PDO::PARAM_INT),
)
);
$this->id = null;
}
public function queue($gearman) {
public function queue() {
$main = RippingCluster_Main::instance();
$config = $main->config();
$log = $main->log();
$log->info('Starting job', $this->id);
// Construct the rip options
$rip_options = array(
@@ -244,16 +242,7 @@ class RippingCluster_Job {
'subtitle_tracks' => $this->subtitle_tracks,
);
// Enqueue this rip
if ( ! $this->id) {
throw new RippingCluster_Exception_LogicException("Rip cannot be queued without being saved!");
}
$task = $gearman->addTask('handbrake_rip', serialize($rip_options), $config->get('rips.context'), $this->id);
if ($task) {
$this->updateStatus(RippingCluster_JobStatus::QUEUED);
} else {
$this->updateStatus(RippingCluster_JobStatus::FAILED);
}
return array('HandBrake', array('rip_options' => $rip_options));
}
protected function loadStatuses() {
@@ -283,6 +272,19 @@ class RippingCluster_Job {
return $new_status;
}
public function isFinished() {
$current_status = $this->currentStatus()->status();
return ($current_status == RippingCluster_JobStatus::COMPLETE || $current_status == RippingCluster_JobStatus::FAILED);
}
public function outputFilesize() {
if (file_exists($this->destination_filename)) {
return filesize($this->destination_filename);
}
return null;
}
public function calculateETA() {
$current_status = $this->currentStatus();
if ($current_status->status() != RippingCluster_JobStatus::RUNNING) {
@@ -292,6 +294,7 @@ class RippingCluster_Job {
$running_time = $current_status->mtime() - $current_status->ctime();
$progress = $current_status->ripProgress();
$remaining_time = 0;
if ($progress > 0) {
$remaining_time = round((100 - $progress) * ($running_time / $progress));
}
@@ -343,7 +346,7 @@ class RippingCluster_Job {
}
public static function runAllJobs() {
RippingCluster_BackgroundTask::run('/usr/bin/php run-jobs.php');
RippingCluster_BackgroundTask::run('/usr/bin/php ' . RippingCluster_Main::makeAbsolutePath('run-jobs.php'));
}
};

View File

@@ -0,0 +1,86 @@
<?php
class RippingCluster_LogEntry extends SihnonFramework_LogEntry {
protected $job_id;
protected static $types;
public static function initialise() {
// Copy the list of datatypes from the parent
// We can't modify it in place, else we'll break any logging done inside the SihnonFramework tree
// or other subclass trees.
static::$types = parent::$types;
// Add the new data types for this subclass
static::$types['job_id'] = 'int';
}
protected function __construct($level, $category, $ctime, $hostname, $progname, $pid, $file, $line, $message, $job_id) {
parent::__construct($level, $category, $ctime, $hostname, $progname, $pid, $file, $line, $message);
$this->job_id = $job_id;
}
public static function fromArray($row) {
return new self(
$row['level'],
$row['category'],
$row['ctime'],
$row['hostname'],
$row['progname'],
$row['pid'],
$row['file'],
$row['line'],
$row['message'],
$row['job_id']
);
}
public function values() {
return array(
$this->level,
$this->category,
$this->ctime,
$this->hostname,
$this->progname,
$this->pid,
$this->file,
$this->line,
$this->message,
$this->job_id,
);
}
public function jobId() {
return $this->job_id;
}
protected static function log($logger, $severity, $job_id, $message, $category = SihnonFramework_Log::CATEGORY_DEFAULT) {
$backtrace = debug_backtrace(false);
$entry = new self($severity, $category, time(), static::$localHostname, static::$localProgname, getmypid(), $backtrace[1]['file'], $backtrace[1]['line'], $message, $job_id);
$logger->log($entry);
}
public static function debug($logger, $job_id, $message, $category = SihnonFramework_Log::CATEGORY_DEFAULT) {
static::log($logger, SihnonFramework_Log::LEVEL_DEBUG, $job_id, $message, $category);
}
public static function info($logger, $job_id, $message, $category = SihnonFramework_Log::CATEGORY_DEFAULT) {
static::log($logger, SihnonFramework_Log::LEVEL_INFO, $job_id, $message, $category);
}
public static function warning($logger, $job_id, $message, $category = SihnonFramework_Log::CATEGORY_DEFAULT) {
static::log($logger, SihnonFramework_Log::LEVEL_WARNING, $job_id, $message, $category);
}
public static function error($logger, $job_id, $message, $category = SihnonFramework_Log::CATEGORY_DEFAULT) {
static::log($logger, SihnonFramework_Log::LEVEL_ERROR, $job_id, $message, $category);
}
}
RippingCluster_LogEntry::initialise();
?>

View File

@@ -0,0 +1,55 @@
<?php
require 'smarty/Smarty.class.php';
class RippingCluster_Main extends SihnonFramework_Main {
protected static $instance;
protected $smarty;
protected $request;
protected function __construct() {
parent::__construct();
$request_string = isset($_GET['l']) ? $_GET['l'] : '';
$this->request = new RippingCluster_RequestParser($request_string);
switch (HBC_File) {
case 'index': {
$smarty_tmp = '/tmp/ripping-cluster';
$this->smarty = new Smarty();
$this->smarty->template_dir = static::makeAbsolutePath('./source/templates');
$this->smarty->compile_dir = static::makeAbsolutePath($smarty_tmp . '/tmp/templates');
$this->smarty->cache_dir = static::makeAbsolutePath($smarty_tmp . '/tmp/cache');
$this->smarty->config_dir = static::makeAbsolutePath($smarty_tmp . '/config');
$this->smarty->registerPlugin('modifier', 'formatDuration', array('RippingCluster_Main', 'formatDuration'));
$this->smarty->registerPlugin('modifier', 'formatFilesize', array('RippingCluster_Main', 'formatFilesize'));
$this->smarty->assign('version', '0.1');
$this->smarty->assign('messages', array());
$this->smarty->assign('base_uri', $this->base_uri);
} break;
}
}
public function smarty() {
return $this->smarty;
}
/**
*
* @return RippingCluster_RequestParser
*/
public function request() {
return $this->request;
}
}
?>

View File

@@ -25,7 +25,7 @@ class RippingCluster_Rips_SourceAudioTrack {
}
public function name() {
return $name;
return $this->name;
}
public function setName($name) {

View File

@@ -87,7 +87,7 @@ class RippingCluster_Source {
}
$longest_index = null;
$maximmum_duration = 0;
$maximum_duration = 0;
if ( ! $this->titles) {
return null;
@@ -105,6 +105,14 @@ class RippingCluster_Source {
return $longest_index;
}
/**
* Permanently deletes this source from disk
*
*/
public function delete() {
RippingCluster_Source_PluginFactory::delete($this->plugin, $this->filename);
}
public function filename() {
return $this->filename;
}

View File

@@ -41,13 +41,21 @@ interface RippingCluster_Source_IPlugin extends RippingCluster_IPlugin {
public static function loadEncoded($encoded_filename, $scan = true, $use_cache = true);
/**
* Determins if a filename is a valid source loadable using this plugin
* Determines if a filename is a valid source loadable using this plugin
*
* @param string $source_filename Filename of the source
* @return bool
*/
public static function isValidSource($source_filename);
/**
* Permanently deletes the given source from disk
*
* @param RippingCluster_Source $source Source object to be deleted
* @return bool
*/
public static function delete($source_filename);
}
?>

View File

@@ -117,6 +117,20 @@ class RippingCluster_Source_Plugin_Bluray extends RippingCluster_PluginBase impl
return true;
}
/**
* Permanently deletes the given source from disk
*
* @param RippingCluster_Source $source Source object to be deleted
* @return bool
*/
public static function delete($source_filename) {
if ( ! self::isValidSource($source_filename)) {
return false;
}
return RippingCluster_Main::rmdir_recursive($source_filename);
}
}
?>

View File

@@ -217,6 +217,21 @@ class RippingCluster_Source_Plugin_HandBrake extends RippingCluster_PluginBase i
return false;
}
/**
* Permanently deletes the given source from disk
*
* @param RippingCluster_Source $source Source object to be deleted
* @return bool
*/
public static function delete($source_filename) {
if ( ! self::isValidSource($source_filename)) {
return false;
}
return RippingCluster_Main::rmdir_recursive($source_filename);
}
}
?>

View File

@@ -244,6 +244,20 @@ class RippingCluster_Source_Plugin_MkvInfo extends RippingCluster_PluginBase imp
return true;
}
/**
* Permanently deletes the given source from disk
*
* @param RippingCluster_Source $source Source object to be deleted
* @return bool
*/
public static function delete($source_filename) {
if ( ! self::isValidSource($source_filename)) {
return false;
}
return unlink($source_filename);
}
}
?>

View File

@@ -2,20 +2,17 @@
class RippingCluster_Source_PluginFactory extends RippingCluster_PluginFactory {
const PLUGIN_DIR = 'RippingCluster/Source/Plugin/';
const PLUGIN_PREFIX = 'RippingCluster_Source_Plugin_';
const PLUGIN_INTERFACE = 'RippingCluster_Source_IPlugin';
protected static $plugin_prefix = 'RippingCluster_Source_Plugin_';
protected static $plugin_interface = 'RippingCluster_Source_IPlugin';
protected static $plugin_dir = array(
RippingCluster_Lib => 'RippingCluster/Source/Plugin/',
);
public static function init() {
}
public static function scan() {
$candidatePlugins = parent::findPlugins(self::PLUGIN_DIR);
self::loadPlugins($candidatePlugins, self::PLUGIN_PREFIX, self::PLUGIN_INTERFACE);
}
public static function enumerate($plugin) {
self::ensureScanned();
@@ -57,7 +54,7 @@ class RippingCluster_Source_PluginFactory extends RippingCluster_PluginFactory {
return call_user_func(array(self::classname($plugin), 'loadEncoded'), $encoded_filename, $scan, $use_cache);
}
public static function isValidSource($plugin, $source_filename) {
/*public static function isValidSource($plugin, $source_filename) {
self::ensureScanned();
if ( ! self::isValidPlugin($plugin)) {
@@ -65,6 +62,22 @@ class RippingCluster_Source_PluginFactory extends RippingCluster_PluginFactory {
}
return call_user_func(array(self::classname($plugin), 'isValidSource'), source_filename);
}*/
/**
* Permanently deletes the given source from disk
*
* @param string $plugin Name of the plugin used to load the source
* @param string $source_filename Filename of the source to be deleted
*/
public static function delete($plugin, $source_filename) {
self::ensureScanned();
if ( ! self::isValidPlugin($plugin)) {
return null;
}
return call_user_func(array(self::classname($plugin), 'delete'), $source_filename);
}
}

View File

@@ -0,0 +1,42 @@
<?php
class RippingCluster_Worker {
protected $gearman;
public function __construct() {
$this->init();
}
private function init() {
if ($this->gearman) {
return;
}
$config = RippingCluster_Main::instance()->config();
$this->gearman = new Net_Gearman_Worker($config->get('rips.job_servers'));
// Load all the plugin classes
RippingCluster_Worker_PluginFactory::scan();
$plugins = RippingCluster_Worker_PluginFactory::getValidPlugins();
foreach ($plugins as $plugin) {
$this->gearman->addAbility($plugin);
}
}
public function start() {
try {
$this->gearman->beginWork();
} catch (Net_Gearman_Exception $e) {
RippingCluster_WorkerLogEntry::error(SihnonFramework_Main::instance()->log(), 0, $e->toText());
return false;
}
return true;
}
}
?>

View File

@@ -0,0 +1,16 @@
<?php
interface RippingCluster_Worker_IPlugin extends RippingCluster_IPlugin {
/**
* Returns the list of functions (and names) implemented by this plugin for registration with Gearman
*
* @return array(string => callback)
*/
//public static function workerFunctions();
//public static function run($args);
}
?>

View File

@@ -51,6 +51,11 @@ class RippingCluster_Worker_Bluray extends RippingCluster_PluginBase implements
$this->job = RippingCluster_Job::fromId($this->rip_options['id']);
}
public static function init() {
// Nothing to do
}
/**
* Returns the list of functions (and names) implemented by this plugin for registration with Gearman
*
@@ -62,14 +67,9 @@ class RippingCluster_Worker_Bluray extends RippingCluster_PluginBase implements
);
}
/**
* Creates an instance of the Worker plugin, and uses it to execute a single job
*
* @param GearmanJob $job Gearman Job object, describing the work to be done
*/
public static function rip(GearmanJob $job) {
$rip = new self($job);
$rip->execute();
public static function run($args) {
//$rip = new self($job);
//$rip->execute();
}
/**

View File

@@ -62,14 +62,9 @@ class RippingCluster_Worker_FfmpegTranscode extends RippingCluster_PluginBase im
);
}
/**
* Creates an instance of the Worker plugin, and uses it to execute a single job
*
* @param GearmanJob $job Gearman Job object, describing the work to be done
*/
public static function rip(GearmanJob $job) {
$rip = new self($job);
$rip->execute();
public static function run($args) {
//$rip = new self($job);
//$rip->execute();
}
/**

View File

@@ -2,19 +2,21 @@
class RippingCluster_Worker_PluginFactory extends RippingCluster_PluginFactory {
const PLUGIN_DIR = 'RippingCluster/Worker/Plugin/';
const PLUGIN_PREFIX = 'RippingCluster_Worker_Plugin_';
const PLUGIN_INTERFACE = 'RippingCluster_Worker_IPlugin';
protected static $plugin_prefix = 'Net_Gearman_Job_';
protected static $plugin_interface = 'RippingCluster_Worker_IPlugin';
protected static $plugin_dir = array(
RippingCluster_Lib => 'Net/Gearman/Job/',
);
public static function init() {
}
public static function scan() {
/* public static function scan() {
$candidatePlugins = parent::findPlugins(self::PLUGIN_DIR);
parent::loadPlugins($candidatePlugins, self::PLUGIN_PREFIX, self::PLUGIN_INTERFACE);
}
}*/
public static function getPluginWorkerFunctions($plugin) {
if ( ! self::isValidPlugin($plugin)) {

View File

@@ -0,0 +1,23 @@
<?php
class RippingCluster_WorkerLogEntry extends RippingCluster_LogEntry {
public static function debug($logger, $job_id, $message) {
static::log($logger, SihnonFramework_Log::LEVEL_DEBUG, $job_id, $message, 'worker');
}
public static function info($logger, $job_id, $message) {
static::log($logger, SihnonFramework_Log::LEVEL_INFO, $job_id, $message, 'worker');
}
public static function warning($logger, $job_id, $message) {
static::log($logger, SihnonFramework_Log::LEVEL_WARNING, $job_id, $message, 'worker');
}
public static function error($logger, $job_id, $message) {
static::log($logger, SihnonFramework_Log::LEVEL_ERROR, $job_id, $message, 'worker');
}
};
?>

View File

@@ -1,6 +1,9 @@
<?php
require_once '../config.php';
require_once RippingCluster_Lib . 'RippingCluster/Main.class.php';
require_once '/etc/ripping-cluster/config.php';
require_once SihnonFramework_Lib . 'SihnonFramework/Main.class.php';
SihnonFramework_Main::registerAutoloadClasses('SihnonFramework', SihnonFramework_Lib,
'RippingCluster', RippingCluster_Lib);
?>

View File

@@ -6,6 +6,7 @@ require '_inc.php';
try {
$main = RippingCluster_Main::instance();
RippingCluster_LogEntry::setLocalProgname('webui');
$smarty = $main->smarty();
$page = new RippingCluster_Page($smarty, $main->request());

View File

@@ -1,13 +0,0 @@
<?php
$job_id = $this->request->get('id');
$job = RippingCluster_Job::fromId($job_id);
$this->smarty->assign('job', $job);
$client_log_entries = RippingCluster_ClientLogEntry::recentForJob($job_id, 30);
$worker_log_entries = RippingCluster_WorkerLogEntry::recentForJob($job_id, 30);
$this->smarty->assign('client_log_entries', $client_log_entries);
$this->smarty->assign('worker_log_entries', $worker_log_entries);
?>

View File

@@ -1,9 +0,0 @@
<?php
$client_log_entries = RippingCluster_ClientLogEntry::recent(30);
$worker_log_entries = RippingCluster_WorkerLogEntry::recent(30);
$this->smarty->assign('client_log_entries', $client_log_entries);
$this->smarty->assign('worker_log_entries', $worker_log_entries);
?>

View File

@@ -2,72 +2,63 @@
define('HBC_File', 'run-jobs');
require_once '../config.php';
require_once RippingCluster_Lib . 'RippingCluster/Main.class.php';
require_once '/etc/ripping-cluster/config.php';
require_once(SihnonFramework_Lib . 'SihnonFramework/Main.class.php');
require_once 'Net/Gearman/Client.php';
SihnonFramework_Main::registerAutoloadClasses('SihnonFramework', SihnonFramework_Lib,
'RippingCluster', RippingCluster_Lib);
try {
$main = RippingCluster_Main::instance();
$config = $main->config();
$log = $main->log();
$gearman = new GearmanClient();
$gearman->addServers($config->get('rips.job_servers'));
$gearman->setCreatedCallback("gearman_created_callback");
$gearman->setDataCallback("gearman_data_callback");
$gearman->setCompleteCallback("gearman_complete_callback");
$gearman->setFailCallback("gearman_fail_callback");
$client = new Net_Gearman_Client($config->get('rips.job_servers'));
$set = new Net_Gearman_Set();
// Retrieve a list of Created jobs
$jobs = RippingCluster_Job::allWithStatus(RippingCluster_JobStatus::CREATED);
foreach ($jobs as $job) {
// Enqueue the job using gearman
$job->queue($gearman);
list($method, $rip_options) = $job->queue();
$task = new Net_Gearman_Task($method, $rip_options);
$task->attachCallback('gearman_complete', Net_Gearman_Task::TASK_COMPLETE);
$task->attachCallback('gearman_fail', Net_Gearman_Task::TASK_FAIL);
$set->addTask($task);
$job->updateStatus(RippingCluster_JobStatus::QUEUED);
}
// Start the job queue
$result = $gearman->runTasks();
if (!$result) {
$log->error($gearman->error());
die($gearman->error());
}
$result = $client->runSet($set);
$log->info("Job queue completed");
RippingCluster_ClientLogEntry::info($log, null, 'Job queue completed', 'batch');
} catch (RippingCluster_Exception $e) {
die("Uncaught Exception (" . get_class($e) . "): " . $e->getMessage() . "\n");
}
function gearman_created_callback($gearman_task) {
function gearman_complete($method, $handle, $result) {
$main = RippingCluster_Main::instance();
$log = $main->log();
$log->info("Job successfully queued with Gearman", $gearman_task->unique());
$job = RippingCluster_Job::fromId($result['id']);
$job->updateStatus(RippingCluster_JobStatus::COMPLETE);
RippingCluster_ClientLogEntry::info($log, $job->id(), 'Job complete');
}
function gearman_data_callback($gearman_task) {
function gearman_fail($task) {
$main = RippingCluster_Main::instance();
$log = $main->log();
$log->debug("Received data callback from Gearman Task");
}
function gearman_complete_callback($gearman_task) {
$main = RippingCluster_Main::instance();
$log = $main->log();
$log->info("Job Complete", $job->id());
}
function gearman_fail_callback($gearman_task) {
$main = RippingCluster_Main::instance();
$log = $main->log();
$job = RippingCluster_Job::fromId($gearman_task->unique());
$job = RippingCluster_Job::fromId($task->arg['rip_options']['id']);
$job->updateStatus(RippingCluster_JobStatus::FAILED);
$log->info("Job Failed", $job->id());
RippingCluster_ClientLogEntry::info($log, $job->id(), 'Job failed');
}

View File

@@ -1,10 +1,12 @@
<?php
$running_jobs = RippingCluster_Job::allWithStatus(RippingCluster_JobStatus::RUNNING, 5);
$completed_jobs = RippingCluster_Job::allWithStatus(RippingCluster_JobStatus::COMPLETE, 5);
$failed_jobs = RippingCluster_Job::allWithStatus(RippingCluster_JobStatus::FAILED, 5);
$running_jobs = RippingCluster_Job::allWithStatus(RippingCluster_JobStatus::RUNNING, 10);
$queued_jobs = RippingCluster_Job::allWithStatus(RippingCluster_JobStatus::QUEUED, 10);
$completed_jobs = RippingCluster_Job::allWithStatus(RippingCluster_JobStatus::COMPLETE, 10);
$failed_jobs = RippingCluster_Job::allWithStatus(RippingCluster_JobStatus::FAILED, 10);
$this->smarty->assign('running_jobs', $running_jobs);
$this->smarty->assign('queued_jobs', $queued_jobs);
$this->smarty->assign('completed_jobs', $completed_jobs);
$this->smarty->assign('failed_jobs', $failed_jobs);

View File

@@ -0,0 +1,15 @@
<?php
$job_id = $this->request->get('id');
$job = RippingCluster_Job::fromId($job_id);
$this->smarty->assign('job', $job);
$log = RippingCluster_Main::instance()->log();
$client_log_entries = RippingCluster_LogEntry::recentEntriesByField($log, 'webui', 'job_id', $job_id, 'ctime', SihnonFramework_Log::ORDER_DESC, 30);
$worker_log_entries = RippingCluster_LogEntry::recentEntriesByField($log, 'worker', 'job_id', $job_id, 'ctime', SihnonFramework_Log::ORDER_DESC, 30);
$this->smarty->assign('client_log_entries', $client_log_entries);
$this->smarty->assign('worker_log_entries', $worker_log_entries);
?>

View File

@@ -4,7 +4,7 @@ $main = RippingCluster_Main::instance();
$req = $main->request();
$config = $main->config();
if ($req->get('submit')) {
if ($req->exists('submit')) {
$action = RippingCluster_Main::issetelse($_POST['action'], 'RippingCluster_Exception_InvalidParameters');
# If a bulk action was selected, the action will be a single term, otherwise it will also contain

View File

@@ -0,0 +1,11 @@
<?php
$log = RippingCluster_Main::instance()->log();
$client_log_entries = RippingCluster_LogEntry::recentEntries($log, 'webui', 'ctime', SihnonFramework_Log::ORDER_DESC, 30);
$worker_log_entries = RippingCluster_LogEntry::recentEntries($log, 'worker', 'ctime', SihnonFramework_Log::ORDER_DESC, 30);
$this->smarty->assign('client_log_entries', $client_log_entries);
$this->smarty->assign('worker_log_entries', $worker_log_entries);
?>

View File

@@ -6,7 +6,7 @@ $config = $main->config();
// Grab the name of this source
$encoded_filename = null;
if ($req->get('submit')) {
if ($req->exists('submit')) {
$encoded_filename = RippingCluster_Main::issetelse($_POST['id'], 'RippingCluster_Exception_InvalidParameters');
// Create the jobs from the request
@@ -17,7 +17,7 @@ if ($req->get('submit')) {
RippingCluster_Page::redirect('rips/setup-rip/queued');
} elseif ($req->get('queued')) {
} elseif ($req->exists('queued')) {
$this->smarty->assign('rips_submitted', true);
} else {

View File

@@ -0,0 +1,28 @@
<?php
$main = RippingCluster_Main::instance();
$req = $main->request();
$config = $main->config();
// Grab the name of this source
$encoded_filename = null;
if ($req->exists('confirm')) {
$plugin = $req->get('plugin', 'RippingCluster_Exception_InvalidParameters');
$encoded_filename = $req->get('id', 'RippingCluster_Exception_InvalidParameters');
$source = RippingCluster_Source_PluginFactory::loadEncoded($plugin, $encoded_filename, false);
$source->delete();
// Redirect back to the sources page
RippingCluster_Page::redirect('rips/sources');
} else {
$plugin = $req->get('plugin', 'RippingCluster_Exception_InvalidParameters');
$encoded_filename = $req->get('id', 'RippingCluster_Exception_InvalidParameters');
$source = RippingCluster_Source_PluginFactory::loadEncoded($plugin, $encoded_filename, false);
$this->smarty->assign('source', $source);
}
?>

View File

@@ -4,7 +4,17 @@
{if $running_jobs}
{foreach from=$running_jobs item=job}
<li><a href="{$base_uri}job-details/id/{$job->id()}" title="View job details">Job {$job->id()}</a></li>
<li><a href="{$base_uri}job-details/id/{$job->id()}" title="View job details">{$job->name()} ({$job->currentStatus()->ripProgress()}%)</a></li>
{/foreach}
{else}
<em>There are no currently running jobs.</em>
{/if}
<h3>Queued Jobs</h3>
{if $queued_jobs}
{foreach from=$queued_jobs item=job}
<li><a href="{$base_uri}job-details/id/{$job->id()}" title="View job details">{$job->name()}</a></li>
{/foreach}
{else}
<em>There are no currently running jobs.</em>
@@ -15,7 +25,7 @@
{if $completed_jobs}
<ul>
{foreach from=$completed_jobs item=job}
<li><a href="{$base_uri}job-details/id/{$job->id()}" title="View job details">Job {$job->id()}</a></li>
<li><a href="{$base_uri}job-details/id/{$job->id()}" title="View job details">{$job->name()}</a></li>
{/foreach}
</ul>
{else}
@@ -27,7 +37,7 @@
{if $failed_jobs}
<ul>
{foreach from=$failed_jobs item=job}
<li><a href="{$base_uri}job-details/id/{$job->id()}" title="View job details">Job {$job->id()}</a></li>
<li><a href="{$base_uri}job-details/id/{$job->id()}" title="View job details">{$job->name()}</a></li>
{/foreach}
</ul>
{else}

View File

@@ -19,13 +19,13 @@
</div>
<div id="navigation">
{include file=navigation.tpl}
{include file="navigation.tpl"}
</div>
<div id="page-container">
<div id="sidebar">
{include file=sidebar.tpl}
{include file="sidebar.tpl"}
</div>
<div id="page">

View File

@@ -43,7 +43,12 @@
{assign var=current_status value=$job->currentStatus()}
<tr>
<td><a href="{$base_uri}job-details/id/{$job->id()}" title="View job details">{$job->name()}</a></td>
<td>{$job->destinationFilename()}</td>
<td>
{$job->destinationFilename()}
{if $job->isFinished()}
({$job->outputFilesize()|formatFilesize})
{/if}
</td>
<td>{$job->title()}</td>
<td>
{$current_status->statusName()}

View File

@@ -17,10 +17,11 @@
{assign var='source_plugin' value=$source->plugin()}
{assign var='source_filename' value=$source->filename()}
{assign var='source_filename_encoded' value=$source->filenameEncoded()}
{assign var='source_cached' value="$source->isCached()}
{assign var='source_cached' value=$source->isCached()}
<li>
[ <a href="{$base_uri}rips/source-details/plugin/{$source_plugin}/id/{$source_filename_encoded}" title="Browse source details">Browse</a> |
<a href="{$base_uri}rips/setup-rip/plugin/{$source_plugin}/id/{$source_filename_encoded}" title="Rip this source">Rip</a> ]
<a href="{$base_uri}rips/setup-rip/plugin/{$source_plugin}/id/{$source_filename_encoded}" title="Rip this source">Rip</a> |
<a href="{$base_uri}sources/delete/plugin/{$source_plugin}/id/{$source_filename_encoded}" title="Delete this source">Delete</a> ]
{$source_filename|escape:'html'}{if $source_cached} (cached){/if}
</li>
{/foreach}

View File

@@ -0,0 +1,7 @@
<h2>Delete Source</h2>
<p>
Are you sure you want to delete {$source->plugin()|escape:"html"}:{$source->filename()|escape:"html"}?
[ <a href="{$base_uri}sources/delete/plugin/{$source->plugin()}/id/{$source->filenameEncoded()}/confirm" title="Delete it">Delete</a>
| <a href="{$base_uri}rips/sources" title="Return to sources list">Cancel</a> ]
</p>

View File

@@ -1,22 +0,0 @@
<?php
define('HBC_File', 'worker');
require_once '../config.php';
require_once RippingCluster_Lib . 'RippingCluster/Main.class.php';
try {
set_time_limit(0);
$main = RippingCluster_Main::instance();
$smarty = $main->smarty();
$worker = new RippingCluster_Worker();
$worker->start();
} catch (RippingCluster_Exception $e) {
die("Uncaught Exception: " . $e->getMessage());
}
?>

View File

@@ -0,0 +1,39 @@
<?php
define('HBC_File', 'worker');
$options = array();
if (isset($_SERVER['argv'])) {
$options = getopt('c:', array('config:'));
}
if (isset($options['config'])) {
require_once $options['config'];
} else {
require_once '/etc/ripping-cluster/config.php';
}
require_once(SihnonFramework_Lib . 'SihnonFramework/Main.class.php');
require_once 'Net/Gearman/Worker.php';
SihnonFramework_Main::registerAutoloadClasses('SihnonFramework', SihnonFramework_Lib,
'RippingCluster', SihnonFramework_Main::makeAbsolutePath(RippingCluster_Lib));
SihnonFramework_Main::registerAutoloadClasses('Net', SihnonFramework_Main::makeAbsolutePath(RippingCluster_Lib));
try {
set_time_limit(0);
$main = RippingCluster_Main::instance();
RippingCluster_LogEntry::setLocalProgname('ripping-cluster-worker');
$smarty = $main->smarty();
$worker = new RippingCluster_Worker();
$worker->start();
} catch (RippingCluster_Exception $e) {
die("Uncaught Exception: " . $e->getMessage());
}
?>