6 Commits

23 changed files with 405 additions and 52 deletions

View File

@@ -67,18 +67,18 @@ class Net_Gearman_Job_HandBrake extends Net_Gearman_Job_Common implements Rippin
// Remove any temporary output files // Remove any temporary output files
if (file_exists($args['temp_output_filename'])) { if (file_exists($args['temp_output_filename'])) {
$result = unlink($args['temp_output_filename']); $result = unlink($args['temp_output_filename']);
if ($result) { if (!$result) {
RippingCluster_WorkerLogEntry::warning($log, $this->job->id(), "Failed to remove temporary output file, still exists at '{$args['temp_output_filename']}'."); RippingCluster_WorkerLogEntry::warning($log, $this->job->id(), "Failed to remove temporary output file, still exists at '{$args['temp_output_filename']}'.");
} }
} }
$this->fail($return_val); $this->fail("Call to HandBrake failed with return code {$return_val}.");
} else { } else {
// Copy the temporary output file to the desired destination // Copy the temporary output file to the desired destination
$move = copy($args['temp_output_filename'], $args['rip_options']['output_filename']); $move = copy($args['temp_output_filename'], $args['rip_options']['output_filename']);
if ($move) { if ($move) {
// Remove the temporary output file // Remove the temporary output file
$result = unlink($args['temp_output_filename']); $result = unlink($args['temp_output_filename']);
if ($result) { if (!$result) {
RippingCluster_WorkerLogEntry::warning($log, $this->job->id(), "Failed to remove temporary output file, still exists at '{$args['temp_output_filename']}'."); RippingCluster_WorkerLogEntry::warning($log, $this->job->id(), "Failed to remove temporary output file, still exists at '{$args['temp_output_filename']}'.");
} }
@@ -90,7 +90,7 @@ class Net_Gearman_Job_HandBrake extends Net_Gearman_Job_Common implements Rippin
} else { } else {
RippingCluster_WorkerLogEntry::error($log, $this->job->id(), "Failed to copy temporary output file to proper destination. File retained as '{$args['temp_output_filename']}'."); 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->job->updateStatus(RippingCluster_JobStatus::FAILED);
$this->fail('-1'); $this->fail('Encode complete, but output file could not be copied to the correct place.');
} }
} }
} }
@@ -133,7 +133,7 @@ class Net_Gearman_Job_HandBrake extends Net_Gearman_Job_Common implements Rippin
$status = $rip->job->currentStatus(); $status = $rip->job->currentStatus();
$status->updateRipProgress($matches[1]); $status->updateRipProgress($matches[1]);
$this->status($matches[1], 100); $this->status($matches[1], 100);
} else { } else if (!preg_match('/^\s+$/', $line)) {
$log = RippingCluster_Main::instance()->log(); $log = RippingCluster_Main::instance()->log();
RippingCluster_WorkerLogEntry::debug($log, $rip->job->id(), $line); RippingCluster_WorkerLogEntry::debug($log, $rip->job->id(), $line);
} }

View File

@@ -17,6 +17,7 @@ class RippingCluster_Main extends SihnonFramework_Main {
$this->request = new RippingCluster_RequestParser($request_string); $this->request = new RippingCluster_RequestParser($request_string);
switch (HBC_File) { switch (HBC_File) {
case 'ajax':
case 'index': { case 'index': {
$smarty_tmp = '/tmp/ripping-cluster'; $smarty_tmp = '/tmp/ripping-cluster';
$this->smarty = new Smarty(); $this->smarty = new Smarty();
@@ -28,10 +29,12 @@ class RippingCluster_Main extends SihnonFramework_Main {
$this->smarty->registerPlugin('modifier', 'formatDuration', array('RippingCluster_Main', 'formatDuration')); $this->smarty->registerPlugin('modifier', 'formatDuration', array('RippingCluster_Main', 'formatDuration'));
$this->smarty->registerPlugin('modifier', 'formatFilesize', array('RippingCluster_Main', 'formatFilesize')); $this->smarty->registerPlugin('modifier', 'formatFilesize', array('RippingCluster_Main', 'formatFilesize'));
$this->smarty->assign('version', '0.1'); $this->smarty->assign('version', '0.2');
$this->smarty->assign('messages', array()); $this->smarty->assign('messages', array());
$this->smarty->assign('base_uri', $this->base_uri); $this->smarty->assign('base_uri', $this->base_uri);
$this->smarty->assign('base_url', static::absoluteUrl(''));
} break; } break;
} }

View File

@@ -1,6 +1,13 @@
<?php <?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 '/etc/ripping-cluster/config.php';
}
require_once SihnonFramework_Lib . 'SihnonFramework/Main.class.php'; require_once SihnonFramework_Lib . 'SihnonFramework/Main.class.php';
SihnonFramework_Main::registerAutoloadClasses('SihnonFramework', SihnonFramework_Lib, SihnonFramework_Main::registerAutoloadClasses('SihnonFramework', SihnonFramework_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

@@ -29,8 +29,12 @@ try {
$set->addTask($task); $set->addTask($task);
$job->updateStatus(RippingCluster_JobStatus::QUEUED); $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 // Start the job queue
$result = $client->runSet($set); $result = $client->runSet($set);
@@ -58,7 +62,7 @@ function gearman_fail($task) {
$job = RippingCluster_Job::fromId($task->arg['rip_options']['id']); $job = RippingCluster_Job::fromId($task->arg['rip_options']['id']);
$job->updateStatus(RippingCluster_JobStatus::FAILED); $job->updateStatus(RippingCluster_JobStatus::FAILED);
RippingCluster_ClientLogEntry::info($log, $job->id(), 'Job failed'); 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

@@ -15,7 +15,7 @@ if ($req->exists('submit')) {
// Spawn the background client process to run all the jobs // Spawn the background client process to run all the jobs
RippingCluster_Job::runAllJobs(); RippingCluster_Job::runAllJobs();
RippingCluster_Page::redirect('rips/setup-rip/queued'); RippingCluster_Page::redirect('rips/setup/queued');
} elseif ($req->exists('queued')) { } elseif ($req->exists('queued')) {
$this->smarty->assign('rips_submitted', true); $this->smarty->assign('rips_submitted', true);

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

@@ -6,9 +6,15 @@
</script> </script>
<link rel="stylesheet" type="text/css" href="{$base_uri}styles/normal.css" /> <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" /> <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-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/3rdparty/jquery-ui-1.8.custom.min.js"></script>
<script type="text/javascript" src="{$base_uri}scripts/main.js"></script>
</head> </head>
<body> <body>
@@ -50,5 +56,23 @@
</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> </body>
</html> </html>

View File

@@ -10,7 +10,7 @@
{else} {else}
<h3>{$source->filename()|escape:"html"}</h3> <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"}" /> <input type="hidden" name="plugin" value="{$source->plugin()|escape:"html"}" />
<fieldset> <fieldset>
<legend>Configure global rip options</legend> <legend>Configure global rip options</legend>

View File

@@ -1,41 +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> |
<a href="{$base_uri}sources/delete/plugin/{$source_plugin}/id/{$source_filename_encoded}" title="Delete this source">Delete</a> ]
{$source_filename|escape:'html'}{if $source_cached} (cached){/if}
</li>
{/foreach}
</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

@@ -5,7 +5,7 @@
<li>Browse <li>Browse
<ul> <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> </ul>
</li> </li>
</ul> </ul>

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; 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 { .default {
background: beige; background: beige;
color: darkgray; color: darkgray;