60 Commits

Author SHA1 Message Date
6648fd8b24 Merge branch 'release-0.2.1' 2011-08-23 02:11:32 +01:00
073da2c4e2 Bumped version to 0.2.1 2011-08-23 02:11:02 +01:00
d474fba2b3 Merge branch 'feature-summaries' into develop 2011-08-23 02:08:40 +01:00
c5eb93dd46 Add hostname to job details worker logs 2011-08-23 01:35:51 +01:00
fbc6f7da48 Add date to job details page. 2011-08-23 01:29:54 +01:00
49e5635a71 Bug fix: save plugin information in job record 2011-08-23 01:28:37 +01:00
8739f6c516 Expose job log display options in the UI 2011-08-23 00:05:21 +01:00
5f786d16d7 Add control of job log message count and sort order via URL 2011-08-22 23:18:57 +01:00
22a3d94dc3 Move job details page and update references 2011-08-22 22:51:46 +01:00
f3415ff57a Merge commit 'release-0.2' into develop 2011-08-22 19:05:40 +01:00
6a57a6fca5 Merge branch 'release-0.2' 2011-08-22 19:04:36 +01:00
a061c23041 Bumped version number to 0.2 2011-08-22 19:03:36 +01:00
4300034afa Merge branch 'feature-ajax' into develop 2011-08-22 18:48:59 +01:00
eb1e330bc4 Added ajax support, and verified with an ajaxified Delete Source feature 2011-08-22 18:47:45 +01:00
41fc0a2cc3 Improve logging in HandBrake worker 2011-08-21 16:12:32 +01:00
95fe2e7641 Add additional logging for batch job runner 2011-08-21 16:11:51 +01:00
8d2ca716df Updated worker to copy rather than move output to final location
This causes ACLs to be inherited from the destination, rather than
preserving permissions from the temporary build location on the local
machine.
2011-08-20 11:07:30 +01:00
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
98 changed files with 1378 additions and 1737 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,145 @@
<?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'])) {
$result = unlink($args['temp_output_filename']);
if (!$result) {
RippingCluster_WorkerLogEntry::warning($log, $this->job->id(), "Failed to remove temporary output file, still exists at '{$args['temp_output_filename']}'.");
}
}
$this->fail("Call to HandBrake failed with return code {$return_val}.");
} else {
// Copy the temporary output file to the desired destination
$move = copy($args['temp_output_filename'], $args['rip_options']['output_filename']);
if ($move) {
// Remove the temporary output file
$result = unlink($args['temp_output_filename']);
if (!$result) {
RippingCluster_WorkerLogEntry::warning($log, $this->job->id(), "Failed to remove temporary output file, still exists at '{$args['temp_output_filename']}'.");
}
//Report success
$this->job->updateStatus(RippingCluster_JobStatus::COMPLETE);
$this->complete( array(
'id' => $this->job->id()
));
} else {
RippingCluster_WorkerLogEntry::error($log, $this->job->id(), "Failed to copy temporary output file to proper destination. File retained as '{$args['temp_output_filename']}'.");
$this->job->updateStatus(RippingCluster_JobStatus::FAILED);
$this->fail('Encode complete, but output file could not be copied to the correct place.');
}
}
}
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 if (!preg_match('/^\s+$/', $line)) {
$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);
}
@@ -183,10 +183,12 @@ class RippingCluster_Job {
$database = RippingCluster_Main::instance()->database();
$database->insert(
'INSERT INTO jobs
(id,name,source,destination,title,format,video_codec,video_width,video_height,quantizer,deinterlace,audio_tracks,audio_codecs,audio_names,subtitle_tracks)
(id,name,source_plugin,rip_plugin,source,destination,title,format,video_codec,video_width,video_height,quantizer,deinterlace,audio_tracks,audio_codecs,audio_names,subtitle_tracks)
VALUES(NULL,:name,:source,:destination,:title,:format,:video_codec,:video_width,:video_height,:quantizer,:deinterlace,:audio_tracks,:audio_codecs,:audio_names,:subtitle_tracks)',
array(
array('name' => 'name', 'value' => $this->name, 'type' => PDO::PARAM_STR),
array('name' => 'source_plugin', 'value' => $this->source_plugin, 'type' => PDO::PARAM_STR),
array('name' => 'rip_plugin', 'value' => $this->rip_plugin, 'type' => PDO::PARAM_STR),
array('name' => 'source', 'value' => $this->source_filename, 'type' => PDO::PARAM_STR),
array('name' => 'destination', 'value' => $this->destination_filename, 'type' => PDO::PARAM_STR),
array('name' => 'title', 'value' => $this->title, 'type' => PDO::PARAM_INT),
@@ -212,18 +214,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 +244,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() {
@@ -282,7 +273,20 @@ 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 +296,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));
}
@@ -329,6 +334,14 @@ class RippingCluster_Job {
public function name() {
return $this->name;
}
public function sourcePlugin() {
return $this->source_plugin;
}
public function ripPlugin() {
return $this->rip_plugin;
}
public function sourceFilename() {
return $this->source_filename;
@@ -343,7 +356,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,58 @@
<?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 'ajax':
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.2.1');
$this->smarty->assign('messages', array());
$this->smarty->assign('base_uri', $this->base_uri);
$this->smarty->assign('base_url', static::absoluteUrl(''));
} 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;
@@ -104,6 +104,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)) {
@@ -25,4 +27,4 @@ class RippingCluster_Worker_PluginFactory extends RippingCluster_PluginFactory {
}
}
?>
?>

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,16 @@
<?php
require_once '../config.php';
require_once RippingCluster_Lib . 'RippingCluster/Main.class.php';
if (isset($_SERVER['RIPPING_CLUSTER_CONFIG']) &&
file_exists($_SERVER['RIPPING_CLUSTER_CONFIG']) &&
is_readable($_SERVER['RIPPING_CLUSTER_CONFIG'])) {
require_once($_SERVER['RIPPING_CLUSTER_CONFIG']);
} else {
require_once '/etc/ripping-cluster/config.php';
}
require_once SihnonFramework_Lib . 'SihnonFramework/Main.class.php';
SihnonFramework_Main::registerAutoloadClasses('SihnonFramework', SihnonFramework_Lib,
'RippingCluster', RippingCluster_Lib);
?>

22
webui/a.php Normal file
View File

@@ -0,0 +1,22 @@
<?php
define('HBC_File', 'ajax');
require '_inc.php';
try {
$main = RippingCluster_Main::instance();
RippingCluster_LogEntry::setLocalProgname('webui');
$smarty = $main->smarty();
$page = new RippingCluster_Page($smarty, $main->request());
if ($page->evaluate()) {
//header('Content-Type: text/json');
$smarty->display('ajax.tpl');
}
} catch (RippingCluster_Exception $e) {
die("Uncaught Exception: " . $e->getMessage());
}
?>

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,67 @@
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);
RippingCluster_ClientLogEntry::info($log, $rip_options['id'], 'Job queued', 'client');
}
$job_count = count($jobs);
RippingCluster_ClientLogEntry::info($log, null, "Job queue started with {$job_count} jobs.", 'batch');
// Start the job queue
$result = $gearman->runTasks();
if (!$result) {
$log->error($gearman->error());
die($gearman->error());
}
$log->info("Job queue completed");
$result = $client->runSet($set);
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) {
$main = RippingCluster_Main::instance();
$log = $main->log();
$log->info("Job successfully queued with Gearman", $gearman_task->unique());
}
function gearman_data_callback($gearman_task) {
function gearman_complete($method, $handle, $result) {
$main = RippingCluster_Main::instance();
$log = $main->log();
$log->debug("Received data callback from Gearman Task");
$job = RippingCluster_Job::fromId($result['id']);
$job->updateStatus(RippingCluster_JobStatus::COMPLETE);
RippingCluster_ClientLogEntry::info($log, $job->id(), 'Job complete');
}
function gearman_complete_callback($gearman_task) {
function gearman_fail($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 with message: {$task->result}");
}

141
webui/scripts/main.js Normal file
View File

@@ -0,0 +1,141 @@
var rc = {
init: function() {
rc.ajax.init();
rc.dialog.init();
rc.page.init();
},
ajax: {
init: function() {
},
get: function(url) {
$.ajax({
url: url,
type: "GET",
dataType: "json",
success: rc.ajax.success,
error: rc.ajax.failure
});
},
post: function(url, data) {
$.ajax(url, {
type: "POST",
dataType: "json",
data: data,
success: rc.ajax.success,
error: rc.ajax.failure
});
},
success: function(d, s, x) {
rc.page.update(d);
rc.dialog.prepare(d);
},
failure: function(x, s, e) {
console.log("Ajax Failure: " + s, e);
console.log(x.responseText);
}
},
dialog: {
init: function() {
$("#dialogheaderclose").click(rc.dialog.close);
},
prepare: function(d) {
if (d.dialog && d.dialog.show) {
if (d.dialog.buttons) {
switch (d.dialog.buttons.type) {
case 'yesno':
$("#dialogfooteryes").click(
function() {
rc.trigger(d.dialog.buttons.actions.yes, d.dialog.buttons.params);
}
);
$("#dialogfooterno").click(
function() {
rc.trigger(d.dialog.buttons.actions.no, d.dialog.buttons.params);
}
);
$("#dialogfooteryesno").show();
break;
}
}
$("#dialog").show();
}
},
close: function() {
$("#dialog").hide();
$(".dialogfooterbuttonset").hide();
$("#dialogcontent").html();
}
},
page: {
init: function() {
},
update: function(d) {
for ( var f in d.page_replacements) {
$("#" + f).html(d.page_replacements[f].content);
}
}
},
sources: {
remove: function(plugin, source) {
rc.ajax.get(base_url + "ajax/delete-source/plugin/" + plugin + "/id/" + source);
},
remove_confirmed: function(plugin, source) {
rc.ajax.get(base_url + "ajax/delete-source/plugin/" + plugin + "/id/" + source + "/confirm/");
}
},
actions: {
'close-dialog': function(params) {
rc.dialog.close();
},
'delete-source-confirm': function(params) {
rc.sources.remove_confirmed(params['plugin'], params['id']);
}
},
trigger: function(action, params) {
// Handle a list of actions by repeated calling self for each argument
if (action instanceof Array) {
for(i in action) {
rc.trigger(action[i], params);
}
return;
}
// Check if action is supported, and execute it
if (rc.actions[action]) {
rc.actions[action](params);
} else {
console.log("Action not supported: " +action);
}
}
};
$(document).ready(rc.init);

View File

@@ -0,0 +1,35 @@
<?php
$main = RippingCluster_Main::instance();
$req = $main->request();
$config = $main->config();
// Grab the name of this source
$encoded_filename = null;
if ($req->exists('confirm')) {
$this->smarty->assign('confirmed', true);
$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();
// Generate a new list of sources to update the page with
$all_sources = RippingCluster_Source_PluginFactory::enumerateAll();
$this->smarty->assign('all_sources', $all_sources);
} else {
$this->smarty->assign('confirmed', false);
$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);
$this->smarty->assign('source_plugin', $plugin);
$this->smarty->assign('source_id', $encoded_filename);
}
?>

View File

@@ -0,0 +1,9 @@
<?php
$main = RippingCluster_Main::instance();
$config = $main->config();
$all_sources = RippingCluster_Source_PluginFactory::enumerateAll();
$this->smarty->assign('all_sources', $all_sources);
?>

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

@@ -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,45 @@
<?php
$main = RippingCluster_Main::instance();
$req = $main->request();
$log = $main->log();
$config = $main->config();
$job_id = $req->get('id');
$job = RippingCluster_Job::fromId($job_id);
$this->smarty->assign('job', $job);
// Fetch log entries for this job
$log_count = $req->get('logs', $config->get('job.logs.default_display_count'));
$default_log_order = $config->get('job.logs.default_order');
$log_order = $req->get('order', $default_log_order);
if ( ! in_array($log_order, array(SihnonFramework_Log::ORDER_ASC, SihnonFramework_Log::ORDER_DESC))) {
$log_order = $default_log_order;
}
$this->smarty->assign('log_order', $log_order);
$this->smarty->assign('log_order_reverse', ($log_order == SihnonFramework_Log::ORDER_ASC ? SihnonFramework_Log::ORDER_DESC : SihnonFramework_Log::ORDER_ASC));
$client_log_entries = array();
$worker_log_entries = array();
$log_count_display = null;
if ($log_count == 'all') {
$log_count_display = 'all';
$log_count = '18446744073709551615'; // see mysql man page for LIMIT
} else if(!is_int($log_count)) {
$log_count = $config->get('job.logs.default_display_count');
$log_count_display = $log_count;
} else {
$log_count_display = $log_count;
}
$client_log_entries = RippingCluster_LogEntry::recentEntriesByField($log, 'webui', 'job_id', $job_id, 'ctime', $log_order, $log_count);
$worker_log_entries = RippingCluster_LogEntry::recentEntriesByField($log, 'worker', 'job_id', $job_id, 'ctime', $log_order, $log_count);
$this->smarty->assign('log_count_display', $log_count_display);
$this->smarty->assign('client_log_entries', $client_log_entries);
$this->smarty->assign('worker_log_entries', $worker_log_entries);
?>

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
@@ -15,9 +15,9 @@ if ($req->get('submit')) {
// Spawn the background client process to run all the jobs
RippingCluster_Job::runAllJobs();
RippingCluster_Page::redirect('rips/setup-rip/queued');
RippingCluster_Page::redirect('rips/setup/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

@@ -0,0 +1,11 @@
{
{if $messages}
messages: [
{foreach from=$messages item=message}
'{$message}',
{/foreach}
],
{/if}
{$page_content}
}

View File

@@ -0,0 +1,37 @@
"page_replacements": {
{if $confirmed}
"source-list": {
{include file="fragments/source-list.tpl" assign="sources_html"}
"content": {$sources_html|json_encode}
}
{else}
"dialogcontent": {
{include file="fragments/delete-source.tpl" assign="delete_source_html"}
"content": {$delete_source_html|json_encode}
}
{/if}
{if ! $confirmed}
},
"dialog": {
"show": true,
"buttons": {
"type": "yesno",
"actions": {
"yes": [
"delete-source-confirm",
"close-dialog"
],
"no": "close-dialog"
},
"params": {
"plugin": {$source_plugin|json_encode},
"id": {$source_id|json_encode}
}
}
}
{else}
}
{/if}

View File

@@ -0,0 +1,6 @@
"page_replacements": {
"source-list": {
{include file="fragments/source-list.tpl" assign="sources_html"}
"content": {$sources_html|json_encode}
}
}

View File

@@ -0,0 +1,3 @@
<p>
Are you sure you want to delete {$source->plugin()|escape:"html"}:{$source->filename()|escape:"html"}?
</p>

View File

@@ -0,0 +1,25 @@
{foreach from=$all_sources key=type item=sources}
<li>{$type}
{if $sources}
<ul>
{foreach from=$sources item=source}
{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()}
<li>
[ <a href="{$base_uri}sources/details/plugin/{$source_plugin}/id/{$source_filename_encoded}" title="Browse source details">Browse</a> |
<a href="{$base_uri}rips/setup/plugin/{$source_plugin}/id/{$source_filename_encoded}" title="Rip this source">Rip</a> |
<a href="javascript:rc.sources.remove('{$source_plugin|escape:'quote'}', '{$source_filename_encoded|escape:'quote'}');" title="Delete this source">Delete</a> ]
{$source_filename|escape:'html'}{if $source_cached} (cached){/if}
</li>
{/foreach}
</ul>
{else}
<p>
<em>There are no {$type} sources available to rip.</em>
</p>
{/if}
</li>
{/foreach}

View File

@@ -0,0 +1,46 @@
<h2>Summary</h2>
<h3>Running Jobs</h3>
{if $running_jobs}
{foreach from=$running_jobs item=job}
<li><a href="{$base_uri}jobs/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}jobs/details/id/{$job->id()}" title="View job details">{$job->name()}</a></li>
{/foreach}
{else}
<em>There are no currently running jobs.</em>
{/if}
<h3>Recently Completed Jobs</h3>
{if $completed_jobs}
<ul>
{foreach from=$completed_jobs item=job}
<li><a href="{$base_uri}jobs/details/id/{$job->id()}" title="View job details">{$job->name()}</a></li>
{/foreach}
</ul>
{else}
<em>There are no recently completed jobs.</em>
{/if}
<h3>Recently Failed Jobs</h3>
{if $failed_jobs}
<ul>
{foreach from=$failed_jobs item=job}
<li><a href="{$base_uri}jobs/details/id/{$job->id()}" title="View job details">{$job->name()}</a></li>
{/foreach}
</ul>
{else}
<em>There are no recently failed jobs.</em>
{/if}

View File

@@ -6,9 +6,15 @@
</script>
<link rel="stylesheet" type="text/css" href="{$base_uri}styles/normal.css" />
<script type="text/javascript">
var base_uri = "{$base_uri|escape:'quote'}";
var base_url = "{$base_url|escape:'quote'}";
</script>
<link type="text/css" href="{$base_uri}styles/3rdparty/jquery-ui/smoothness/jquery-ui-1.8.custom.css" rel="Stylesheet" />
<script type="text/javascript" src="{$base_uri}scripts/3rdparty/jquery-1.4.2.js"></script>
<script type="text/javascript" src="{$base_uri}scripts/3rdparty/jquery-ui-1.8.custom.min.js"></script>
<script type="text/javascript" src="{$base_uri}scripts/main.js"></script>
</head>
<body>
@@ -19,13 +25,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">
@@ -49,6 +55,24 @@
</div>
</div>
<div id="centrepoint">
<div id="dialog">
<div id="dialogheader">
<div id="dialogheadertitle">Dialog</div>
<div id="dialogheaderclose">X</div>
</div>
<div id="dialogcontent"></div>
<div id="dialogfooter">
<div id="dialogfooteryesno" class="dialogfooterbuttonset">
<fieldset>
<input type="button" class="dialogbutton" id="dialogfooteryes" value="Yes" />
<input type="button" class="dialogbutton" id="dialogfooterno" value="No" />
</fieldset>
</div>
</div>
</div>
</div>
</body>
</html>

View File

@@ -42,8 +42,13 @@
{foreach from=$jobs item=job}
{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><a href="{$base_uri}jobs/details/id/{$job->id()}" title="View job details">{$job->name()}</a></td>
<td>
{$job->destinationFilename()}
{if $job->isFinished()}
({$job->outputFilesize()|formatFilesize})
{/if}
</td>
<td>{$job->title()}</td>
<td>
{$current_status->statusName()}

View File

@@ -0,0 +1,91 @@
<h2>Job Details</h2>
<h3>Summary</h3>
<dl>
<dt>Source Plugin</dt>
<dd>{$job->sourcePlugin()}</dd>
<dt>Rip Plugin</dt>
<dd>{$job->ripPlugin()}</dd>
<dt>Source Filename</dt>
<dd>{$job->sourceFilename()}</dd>
<dt>Source Title</dt>
<dd>{$job->title()}</dd>
<dt>Status</dt>
<dd>{$job->currentStatus()->statusName()} ({$job->currentStatus()->mtime()|date_format:'%Y-%m-%d %H:%M:%S'})</dd>
<dt>Destination Filename</dt>
<dd>{$job->destinationFilename()}</dd>
{if $job->isFinished()}
<dt>Destination Filesize</dt>
<dd>{$job->outputFilesize()|formatFilesize}</dd>
{/if}
</dl>
<h3>Log messages</h3>
<h4>Options</h4>
<ul>
{if $log_count_display eq 'all'}
<li><a href="{$base_uri}jobs/details/id/{$job->id()}/order/{$log_order}/" title="View recent logs only">View recent messages only</a></li>
{else}
<li><a href="{$base_uri}jobs/details/id/{$job->id()}/logs/all/" title="View all logs">View all messages</a></li>
{/if}
<li><a href="{$base_uri}jobs/details/id/{$job->id()}/logs/{$log_count_display}/order/{$log_order_reverse}/" title="Reverse display order of log messages">Reverse display order</a></li>
</ul>
<h4>Recent Client Logs</h4>
{if $client_log_entries}
<table>
<thead>
<tr>
<th>Level</th>
<th>Time</th>
<th>Message</th>
</tr>
</thead>
<tbody>
{foreach from=$client_log_entries item=log_entry}
<tr>
<td>{$log_entry->level()}</td>
<td>{$log_entry->ctime()|date_format:"%Y-%m-%d %H:%M:%S"}</td>
<td>{$log_entry->message()}</td>
</tr>
{/foreach}
</tbody>
</table>
{else}
<em>There are no client log entries.</em>
{/if}
<h4>Recent Worker Logs</h4>
{if $worker_log_entries}
<table>
<thead>
<tr>
<th>Level</th>
<th>Time</th>
<th>Hostname</th>
<th>Message</th>
</tr>
</thead>
<tbody>
{foreach from=$worker_log_entries item=log_entry}
<tr>
<td>{$log_entry->level()}</td>
<td>{$log_entry->ctime()|date_format:"%Y-%m-%d %H:%M:%S"}</td>
<td>{$log_entry->hostname()}</td>
<td>{$log_entry->message()}</td>
</tr>
{/foreach}
</tbody>
</table>
{else}
<em>There are no worker log entries.</em>
{/if}

View File

@@ -10,7 +10,7 @@
{else}
<h3>{$source->filename()|escape:"html"}</h3>
<form name="setup-rips" id="setup-rips" action="{$base_uri}rips/setup-rip/submit/" method="post">
<form name="setup" id="setup-rips" action="{$base_uri}rips/setup/submit/" method="post">
<input type="hidden" name="plugin" value="{$source->plugin()|escape:"html"}" />
<fieldset>
<legend>Configure global rip options</legend>

View File

@@ -5,7 +5,7 @@
<li>Browse
<ul>
<li><a href="{$base_uri}rips/sources" title="Browse Sources">Sources</a></li>
<li><a href="{$base_uri}sources/list" title="Browse Sources">Sources</a></li>
</ul>
</li>
</ul>

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

@@ -0,0 +1,18 @@
<h2>Sources</h2>
{if $all_sources}
<p>
The list below contains all the DVD sources that are available and ready for ripping.
</p>
<p>
Sources that have recently been scanned are marked <em>(cached)</em> and will load fairly quickly.
Sources that have not been cached will be scanned when the link is clicked, and this may take several minutes so please be patient.
</p>
<ul id="source-list">
{include file="fragments/source-list.tpl"}
</ul>
{else}
<p>
<em>There are currently no sources available to rip.</em>
</p>
{/if}

View File

@@ -93,6 +93,55 @@ label {
margin: 1em;
}
/* Centred dialog taken from http://stackoverflow.com/questions/1205457/how-to-design-a-css-for-a-centered-floating-confirm-dialog */
#centrepoint {
top: 50%;
left: 50%;
position: absolute;
}
#dialog {
position: relative;
width: 600px;
margin-left: -300px;
/*height: 20em;*/
margin-top: -20em;
display: none;
background: #eeeeee;
border: 2px solid #a7a09a;
}
#dialogheader {
height: 2em;
width: 100%;
margin: 0.3em;
}
#dialogheadertitle {
color: black;
font-weight: bold;
float: left;
}
#dialogheaderclose {
width: 1.2em;
height: 1.2em;
background-color: crimson;
color: white;
border: 1px solid fireBrick;
float: right;
margin-right: 1em;
text-align: center;
vertical-align: middle;
display: table-cell;
font-weight: bold;
cursor: pointer;
}
#dialogcontent {
padding: 0.5em;
}
.dialogfooterbuttonset {
display: none;
text-align: right;
}
.default {
background: beige;
color: darkgray;

View File

@@ -1,36 +0,0 @@
<h2>Summary</h2>
<h3>Running Jobs</h3>
{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>
{/foreach}
{else}
<em>There are no currently running jobs.</em>
{/if}
<h3>Recently Completed Jobs</h3>
{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>
{/foreach}
</ul>
{else}
<em>There are no recently completed jobs.</em>
{/if}
<h3>Recently Failed Jobs</h3>
{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>
{/foreach}
</ul>
{else}
<em>There are no recently failed jobs.</em>
{/if}

View File

@@ -1,57 +0,0 @@
<h2>Job Details</h2>
<h3>Summary</h3>
<em>Summary details here</em>
<h3>Recent Client Logs</h3>
{if $client_log_entries}
<table>
<thead>
<tr>
<th>Level</th>
<th>Time</th>
<th>Message</th>
</tr>
</thead>
<tbody>
{foreach from=$client_log_entries item=log_entry}
<tr>
<td>{$log_entry->level()}</td>
<td>{$log_entry->ctime()|date_format:"%Y-%m-%d %H:%M:%S"}</td>
<td>{$log_entry->message()}</td>
</tr>
{/foreach}
</tbody>
</table>
{else}
<em>There are no client log entries.</em>
{/if}
<h3>Recent Worker Logs</h3>
{if $worker_log_entries}
<table>
<thead>
<tr>
<th>Level</th>
<th>Time</th>
<th>Message</th>
</tr>
</thead>
<tbody>
{foreach from=$worker_log_entries item=log_entry}
<tr>
<td>{$log_entry->level()}</td>
<td>{$log_entry->ctime()|date_format:"%Y-%m-%d %H:%M:%S"}</td>
<td>{$log_entry->message()}</td>
</tr>
{/foreach}
</tbody>
</table>
{else}
<em>There are no worker log entries.</em>
{/if}

View File

@@ -1,40 +0,0 @@
<h2>Sources</h2>
{if $all_sources}
<p>
The list below contains all the DVD sources that are available and ready for ripping.
</p>
<p>
Sources that have recently been scanned are marked <em>(cached)</em> and will load fairly quickly.
Sources that have not been cached will be scanned when the link is clicked, and this may take several minutes so please be patient.
</p>
<ul>
{foreach from=$all_sources key=type item=sources}
<li>{$type}
{if $sources}
<ul>
{foreach from=$sources item=source}
{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()}
<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> ]
{$source_filename|escape:'html'}{if $source_cached} (cached){/if}
</li>
{/foreach}
</ul>
{else}
<p>
<em>There are no {$type} sources available to rip.</em>
</p>
{/if}
</li>
{/foreach}
</ul>
{else}
<p>
<em>There are currently no sources available to rip.</em>
</p>
{/if}

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());
}
?>