Various UI improvements

This commit is contained in:
2010-08-14 12:19:50 +01:00
parent a5282ebe5a
commit 5aa4bf11c7
13 changed files with 255 additions and 19 deletions

View File

@@ -24,4 +24,7 @@ class HandBrakeCluster_Exception_CacheException extends HandBrakeCluster
class HandBrakeCluster_Exception_InvalidCacheDir extends HandBrakeCluster_Exception_CacheException {}; class HandBrakeCluster_Exception_InvalidCacheDir extends HandBrakeCluster_Exception_CacheException {};
class HandBrakeCluster_Exception_CacheObjectNotFound extends HandBrakeCluster_Exception_CacheException {}; class HandBrakeCluster_Exception_CacheObjectNotFound extends HandBrakeCluster_Exception_CacheException {};
class HandBrakeCluster_Exception_LogicException extends HandBrakeCluster_Exception {};
class HandBrakeCluster_Exception_JobNotRunning extends HandBrakeCluster_Exception_LogicException {};
?> ?>

View File

@@ -44,6 +44,12 @@ class HandBrakeCluster_Job {
$this->subtitle_tracks = $subtitle_tracks; $this->subtitle_tracks = $subtitle_tracks;
} }
public function __clone() {
$this->id = null;
$this->create();
}
public static function fromDatabaseRow($row) { public static function fromDatabaseRow($row) {
return new HandBrakeCluster_Job( return new HandBrakeCluster_Job(
HandBrakeCluster_Rips_Source::load($rips['source']), HandBrakeCluster_Rips_Source::load($rips['source']),
@@ -124,12 +130,14 @@ class HandBrakeCluster_Job {
$jobs = array(); $jobs = array();
foreach ($titles as $title => $details) { foreach ($titles as $title => $details) {
if (HandBrakeCluster_Main::issetelse($details['queue'])) { if (HandBrakeCluster_Main::issetelse($details['queue'])) {
HandBrakeCluster_Main::issetelse($details['output_filename'], HandBrakeCluster_Exception_InvalidParameters);
$job = new HandBrakeCluster_Job( $job = new HandBrakeCluster_Job(
$source, $source,
null, null,
HandBrakeCluster_Main::issetelse($details['name'], 'unnamed job'), HandBrakeCluster_Main::issetelse($details['name'], 'unnamed job'),
$source->filename(), $source->filename(),
HandBrakeCluster_Main::issetelse($details['output_filename'], HandBrakeCluster_Exception_InvalidParameters), $global_options['output-directory'] . DIRECTORY_SEPARATOR . $details['output_filename'],
$title, $title,
$global_options['format'], $global_options['format'],
$global_options['video-codec'], $global_options['video-codec'],
@@ -179,6 +187,18 @@ class HandBrakeCluster_Job {
$status = HandBrakeCluster_JobStatus::updateStatusForJob($this, HandBrakeCluster_JobStatus::CREATED); $status = HandBrakeCluster_JobStatus::updateStatusForJob($this, HandBrakeCluster_JobStatus::CREATED);
} }
public function delete() {
$database = HandBrakeCluster_Main::instance()->database();
$database->update(
'DELETE FROM jobs WHERE id=:job_id LIMIT 1',
array(
array(name => 'job_id', value => $this->id, type => PDO::PARAM_INT),
)
);
$this->id = null;
}
public function queue($gearman) { public function queue($gearman) {
$main = HandBrakeCluster_Main::instance(); $main = HandBrakeCluster_Main::instance();
$config = $main->config(); $config = $main->config();
@@ -188,8 +208,8 @@ class HandBrakeCluster_Job {
// Construct the rip options // Construct the rip options
$rip_options = array( $rip_options = array(
'nice' => $config->get('rips.nice', 15), 'nice' => $config->get('rips.nice', 15),
'input_filename' => dirname($this->source_filename) . DIRECTORY_SEPARATOR . basename($this->source_filename), 'input_filename' => $this->source_filename,
'output_filename' => dirname($this->destination_filename) . DIRECTORY_SEPARATOR . basename($this->destination_filename), 'output_filename' => $this->destination_filename,
'title' => $this->title, 'title' => $this->title,
'format' => $this->format, 'format' => $this->format,
'video_codec' => $this->video_codec, 'video_codec' => $this->video_codec,
@@ -233,6 +253,22 @@ class HandBrakeCluster_Job {
return HandBrakeCluster_JobStatus::updateStatusForJob($this, $new_status, $rip_progress); return HandBrakeCluster_JobStatus::updateStatusForJob($this, $new_status, $rip_progress);
} }
public function calculateETA() {
$current_status = $this->currentStatus();
if ($current_status->status() != HandBrakeCluster_JobStatus::RUNNING) {
throw new HandBrakeCluster_Exception_JobNotRunning();
}
$running_time = $current_status->mtime() - $current_status->ctime();
$progress = $current_status->ripProgress();
if ($progress > 0) {
$remaining_time = round((100 - $progress) * ($running_time / $progress));
}
return $remaining_time;
}
public function id() { public function id() {
return $this->id; return $this->id;
} }

View File

@@ -32,6 +32,8 @@ class HandBrakeCluster_Main {
$this->smarty->cache_dir = './tmp/cache'; $this->smarty->cache_dir = './tmp/cache';
$this->smarty->config_fir = './config'; $this->smarty->config_fir = './config';
$this->smarty->register_modifier('formatDuration', array('HandBrakeCluster_Main', 'formatDuration'));
$this->smarty->assign('version', '0.1'); $this->smarty->assign('version', '0.1');
$this->base_uri = dirname($_SERVER['SCRIPT_NAME']) . '/'; $this->base_uri = dirname($_SERVER['SCRIPT_NAME']) . '/';
@@ -160,13 +162,40 @@ class HandBrakeCluster_Main {
return $var; return $var;
} }
if (preg_match('/^HandBrakeCluster_Exception/', $default) && class_exists($default) && is_subclass_of($default, HandBrakeCluster_Exception)) { if (is_string($default) && preg_match('/^HandBrakeCluster_Exception/', $default) && class_exists($default) && is_subclass_of($default, HandBrakeCluster_Exception)) {
throw new $default(); throw new $default();
} }
return $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(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;
}
} }
HandBrakeCluster_Main::initialise(); HandBrakeCluster_Main::initialise();

View File

@@ -72,12 +72,16 @@ class HandBrakeCluster_RequestParser {
return join('/', $this->page); return join('/', $this->page);
} }
public function exists($key) {
return isset($this->vars[$key]);
}
public function get($key, $default = null) { public function get($key, $default = null) {
if (isset($this->vars[$key])) { if (isset($this->vars[$key])) {
return $this->vars[$key]; return $this->vars[$key];
} }
if (is_subclass_of($default, HandBrakeCluster_Exception)) { if (is_string($default) && preg_match('/^HandBrakeCluster_Exception/', $default) && class_exists($default) && is_subclass_of($default, HandBrakeCluster_Exception)) {
throw new $default(); throw new $default();
} }

View File

@@ -131,7 +131,7 @@ class HandBrakeCluster_Rips_Source {
$title->addChapter($matches['id'], $matches['duration']); $title->addChapter($matches['id'], $matches['duration']);
} break; } break;
case $title && $mode == self::PM_AUDIO && preg_match('/^ \+ (?P<id>\d+), (?P<name>.+) \((?P<format>.+)\) \((?P<channels>.+) ch\) \((?P<language>.+)\), (?P<samplerate>\d+)Hz, (?P<bitrate>\d+)bps$/', $line, $matches): { case $title && $mode == self::PM_AUDIO && preg_match('/^ \+ (?P<id>\d+), (?P<name>.+) \((?P<format>.+)\) \((?P<channels>(.+ ch|Dolby Surround))\) \((?P<language>.+)\), (?P<samplerate>\d+)Hz, (?P<bitrate>\d+)bps$/', $line, $matches): {
$title->addAudioTrack( $title->addAudioTrack(
new HandBrakeCluster_Rips_SourceAudioTrack( new HandBrakeCluster_Rips_SourceAudioTrack(
$matches['id'], $matches['name'], $matches['format'], $matches['channels'], $matches['id'], $matches['name'], $matches['format'], $matches['channels'],
@@ -198,6 +198,26 @@ class HandBrakeCluster_Rips_Source {
return $longest_title; return $longest_title;
} }
public function longestTitleIndex() {
$longest_index = null;
$maximmum_duration = 0;
if ( ! $this->titles) {
return null;
}
for ($i = 0, $l = count($this->titles); $i < $l; ++$i) {
$title = $this->titles[$i];
$duration = $title->durationInSeconds();
if ($duration > $maximum_duration) {
$longest_index = $i;
$maximum_duration = $duration;
}
}
return $longest_index;
}
public function filename() { public function filename() {
return $this->source; return $this->source;
} }

BIN
images/caution.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

BIN
images/redo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

BIN
images/trash.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

@@ -1,6 +1,90 @@
<?php <?php
$main = HandBrakeCluster_Main::instance();
$req = $main->request();
$config = $main->config();
if ($req->get('submit')) {
$action = HandBrakeCluster_Main::issetelse($_POST['action'], HandBrakeCluster_Exception_InvalidParameters);
# If a bulk action was selected, the action will be a single term, otherwise it will also contain
# the id of the single item to act upon. Work out which was used now.
$matches = $job_ids = array();
if (preg_match('/^(.*)\[(\d+)\]$/', $action, $matches)) {
$action = $matches[1];
$job_ids = array($matches[2]);
}
else {
$job_ids = $_POST['include'];
}
$jobs = array();
foreach ($job_ids as $job_id) {
$job = HandBrakeCluster_Job::fromId($job_id);
if (!$job) {
throw new HandBrakeCluster_Exception_InvalidParameters('job_id');
}
$jobs[] = $job;
}
switch ($action) {
case 'mark-failed': {
foreach ($jobs as $job) {
$job->updateStatus(HandBrakeCluster_JobStatus::FAILED);
}
} break;
case 'retry': {
# Clone each of the selected jobs
foreach ($jobs as $job) {
$new_job = clone $job;
}
# Dispatch all the jobs in one run
HandBrakeCluster_Job::runAllJobs();
# Redirect to the job queued page to show the jobs were successfully dispatched
HandBrakeCluster_Page::redirect('rips/setup-rip/queued');
} break;
case 'delete': {
foreach ($jobs as $job) {
$job->delete();
}
} break;
default: {
throw new HandBrakeCluster_Exception_InvalidParameters('action');
}
}
HandBrakeCluster_Page::redirect('jobs');
} else {
if (isset($_POST['view'])) {
$statusName = urlencode($_POST['view']);
HandBrakeCluster_Page::redirect("jobs/view/{$statusName}");
}
$statusName = $req->get('view', 'any');
switch ($statusName) {
case 'any': $status = null; break;
case 'queued': $status = HandBrakeCluster_JobStatus::QUEUED; break;
case 'running': $status = HandBrakeCluster_JobStatus::RUNNING; break;
case 'complete': $status = HandBrakeCluster_JobStatus::COMPLETE; break;
case 'failed': $status = HandBrakeCluster_JobStatus::FAILED; break;
default: throw new HandBrakeCluster_Exception_InvalidParameters('view');
}
$jobs = array();
if ($status) {
$jobs = HandBrakeCluster_Job::allWithStatus($status);
} else {
$jobs = HandBrakeCluster_Job::all(); $jobs = HandBrakeCluster_Job::all();
}
$this->smarty->assign('jobs', $jobs); $this->smarty->assign('jobs', $jobs);
}
?> ?>

View File

@@ -29,6 +29,7 @@ if ($req->get('submit')) {
$this->smarty->assign('source', $source); $this->smarty->assign('source', $source);
$this->smarty->assign('titles', $source->titles()); $this->smarty->assign('titles', $source->titles());
$this->smarty->assign('longest_title', $source->longestTitle()); $this->smarty->assign('longest_title', $source->longestTitle());
$this->smarty->assign('default_output_directory', $config->get('rips.default.output_directory'));
} }
?> ?>

View File

@@ -104,8 +104,8 @@ label {
width: 16px; width: 16px;
} }
table#setup-rips input,select { form#setup-rips input[type="text"] {
width: 30em;
} }
#quantizer-slider { #quantizer-slider {

View File

@@ -1,6 +1,32 @@
<h2>Jobs</h2> <h2>Jobs</h2>
{if $jobs} {if $jobs}
<form name="view-jobs" id="view-jobs" action="{$base_uri}jobs" method="post">
<fieldset>
<legend>View</legend>
<label for="view-status">View only jobs with status:</label>
<select id="view-status" name="view">
<option value="any">Any Status</option>
<option value="queued">Queued</option>
<option value="running">Running</option>
<option value="complete">Complete</option>
<option value="failed">Failed</option>
</select>
<input type="submit" name="submit" value="view" />
</fieldset>
</form>
<form name="manage-jobs" id="manage-jobs" action="{$base_uri}jobs/submit" method="post">
<fieldset>
<legend>Bulk Actions</legend>
<input type="image" class="icon" name="action" id="mark-failed-top" value="mark-failed" src="{$base_uri}images/caution.png" alt="Mark all marked jobs as failed" />
<input type="image" class="icon" name="action" id="redo-top" value="retry" src="{$base_uri}images/redo.png" alt="Repeat all marked jobs" />
<input type="image" class="icon" name="action" id="delete-top" value="delete" src="{$base_uri}images/trash.png" alt="Delete all marked jobs" />
</fieldset>
<table> <table>
<thead> <thead>
<tr> <tr>
@@ -8,20 +34,46 @@
<th>Destination</th> <th>Destination</th>
<th>Title</th> <th>Title</th>
<th>Status</th> <th>Status</th>
<th>Actions</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{foreach from=$jobs item=job} {foreach from=$jobs item=job}
{assign var=current_status value=$job->currentStatus()} {assign var=current_status value=$job->currentStatus()}
<tr> <tr>
<td><a href="{$base_uri}/job-details/id/{$job->id()}" title="View job details">{$job->name()}</a></td> <td><a href="{$base_uri}job-details/id/{$job->id()}" title="View job details">{$job->name()}</a></td>
<td>{$job->destinationFilename()}</td> <td>{$job->destinationFilename()}</td>
<td>{$job->title()}</td> <td>{$job->title()}</td>
<td>{$current_status->statusName()}{if $current_status->hasProgressInfo()} ({$current_status->ripProgress()}%, <em>last updated {$current_status->mtime()|date_format:"%D %T"}</em>){/if}</td> <td>
{$current_status->statusName()}
{if $current_status->hasProgressInfo()}
<br />
Progress: {$current_status->ripProgress()}%<br />
At: <em>{$current_status->mtime()|date_format:"%D %T"}</em><br />
ETA: <em>{$job->calculateETA()|formatDuration}</em>
{/if}
</td>
<td>
<fieldset>
<input type="checkbox" name="include[]" value="{$job->id()}" />
<input type="image" class="icon" name="action" id="mark-failed-{$job->id()}" value="mark-failed[{$job->id()}]" src="{$base_uri}images/caution.png" alt="Mark job as failed" />
<input type="image" class="icon" name="action" id="redo-{$job->id()}" value="retry[{$job->id()}]" src="{$base_uri}images/redo.png" alt="Repeat job" />
<input type="image" class="icon" name="action" id="delete-{$job->id()}" value="delete[{$job->id()}]" src="{$base_uri}images/trash.png" alt="Delete job" />
</fieldset>
</td>
</tr> </tr>
{/foreach} {/foreach}
</tbody> </tbody>
</table> </table>
<fieldset>
<legend>Bulk Actions</legend>
<input type="image" class="icon" name="action" id="mark-failed-bottom" value="mark-failed" src="{$base_uri}images/caution.png" alt="Mark all marked jobs as failed" />
<input type="image" class="icon" name="action" id="redo-bottom" value="retry" src="{$base_uri}images/redo.png" alt="Repeat all marked jobs" />
<input type="image" class="icon" name="action" id="delete-bottom" value="delete" src="{$base_uri}images/trash.png" alt="Delete all marked jobs" />
</fieldset>
</form>
{else} {else}
<em>There are no jobs</em> <em>There are no jobs</em>
{/if} {/if}

View File

@@ -8,6 +8,8 @@
to see a list of running jobs, or the <a href="{$base_uri}logs/" title="View logs">logs</a> page for more detailed progress information. to see a list of running jobs, or the <a href="{$base_uri}logs/" title="View logs">logs</a> page for more detailed progress information.
</p> </p>
{else} {else}
<h3>{$source->filename()|htmlspecialchars}</h3>
<form name="setup-rips" id="setup-rips" action="{$base_uri}rips/setup-rip/submit/" method="post"> <form name="setup-rips" id="setup-rips" action="{$base_uri}rips/setup-rip/submit/" method="post">
<fieldset> <fieldset>
<legend>Configure global rip options</legend> <legend>Configure global rip options</legend>
@@ -16,7 +18,7 @@
<div> <div>
<label for="global-output-directory">Output directory</label> <label for="global-output-directory">Output directory</label>
<input type="text" id="global-ouput-directory" name="rip-options[output-directory]" value="{$default_output_dir}" /> <input type="text" id="global-ouput-directory" name="rip-options[output-directory]" value="{$default_output_directory}" />
</div> </div>
<div> <div>
@@ -58,7 +60,7 @@
<div id="available-titles"> <div id="available-titles">
{foreach from=$titles item=title} {foreach from=$titles item=title}
<h3><a href="#">Title {$title->id()} (Duration: {$title->duration()}, Chapters: {$title->chapterCount()})</a></h3> <h3 id="configure-rip-{$title->id()}"><a href="#">Title {$title->id()} (Duration: {$title->duration()}, Chapters: {$title->chapterCount()})</a></h3>
<div id="rips-{$title->id()}"> <div id="rips-{$title->id()}">
<fieldset> <fieldset>
<legend>Configure title rip options</legend> <legend>Configure title rip options</legend>
@@ -68,11 +70,16 @@
<input type="checkbox" id="rip-title-{$title->id()}" name="rips[{$title->id()}][queue]" value="1" /> <input type="checkbox" id="rip-title-{$title->id()}" name="rips[{$title->id()}][queue]" value="1" />
</div> </div>
<div>
<label for="rip-name-{$title->id()}">Short Name</label>
<input type="text" id="rip-name-{$title->id()}" name="rips[{$title->id()}][name]" value="" />
</div>
<div> <div>
<label for="rip-audio-{$title->id()}">Audio tracks</label> <label for="rip-audio-{$title->id()}">Audio tracks</label>
<select id="rip-audio-{$title->id()}" name="rips[{$title->id()}][audio][]" size="5" multiple="multiple" class="rip-streams"> <select id="rip-audio-{$title->id()}" name="rips[{$title->id()}][audio][]" size="5" multiple="multiple" class="rip-streams">
{foreach from=$title->audioTracks() item=audio} {foreach from=$title->audioTracks() item=audio}
<option value="{$audio->id()}">{$audio->name()} - {$audio->channels()} ch ({$audio->language()}) </option> <option value="{$audio->id()}">{$audio->name()} - {$audio->channels()} ({$audio->language()}) </option>
{/foreach} {/foreach}
</select> </select>
@@ -142,7 +149,7 @@
{literal} {literal}
<script language="javascript"> <script language="javascript">
$(function() { $(function() {
$("#available-titles").accordion(); $("#available-titles").accordion({active: {/literal}{$source->longestTitleIndex()}{literal}});
$("input:submit").button(); $("input:submit").button();
$("#quantizer-slider").slider({ $("#quantizer-slider").slider({
value:0.61, value:0.61,