/usr/local/CPAN/dvdrip/Video/DVDRip/GUI/Cluster/Control.pm
# $Id: Control.pm 2303 2007-04-13 11:23:39Z joern $
#-----------------------------------------------------------------------
# Copyright (C) 2001-2006 Jörn Reder <joern AT zyn.de>.
# All Rights Reserved. See file COPYRIGHT for details.
#
# This module is part of Video::DVDRip, which is free software; you can
# redistribute it and/or modify it under the same terms as Perl itself.
#-----------------------------------------------------------------------
package Video::DVDRip::GUI::Cluster::Control;
use Locale::TextDomain qw (video.dvdrip);
use base Video::DVDRip::GUI::Base;
use Gtk2::Helper;
use Event::RPC 0.89;
use Event::RPC::Client;
use Video::DVDRip::GUI::Cluster::Node;
use Video::DVDRip::GUI::Cluster::Title;
use strict;
use Carp;
sub rpc_client { shift->{rpc_client} }
sub master { shift->{master} }
sub log_socket { shift->{log_socket} }
sub gtk_log_view { shift->{gtk_log_view} }
sub gtk_log_buffer { shift->{gtk_log_buffer} }
sub exit_on_close { shift->{exit_on_close} }
sub selected_project_id { shift->{selected_project_id} }
sub selected_job_id { shift->{selected_job_id} }
sub selected_node_name { shift->{selected_node_name} }
sub master_event_queue { shift->{master_event_queue} }
sub log_watcher { shift->{log_watcher} }
sub event_timeout { shift->{event_timeout} }
sub node_gui { shift->{node_gui} }
sub cluster_ff { shift->{cluster_ff} }
sub pane1_pos { shift->{pane1_pos} }
sub pane2_pos { shift->{pane2_pos} }
sub pane3_pos { shift->{pane3_pos} }
sub window_height { shift->{window_height} }
sub window_width { shift->{window_width} }
sub set_rpc_client { shift->{rpc_client} = $_[1] }
sub set_master { shift->{master} = $_[1] }
sub set_log_socket { shift->{log_socket} = $_[1] }
sub set_gtk_log_view { shift->{gtk_log_view} = $_[1] }
sub set_gtk_log_buffer { shift->{gtk_log_buffer} = $_[1] }
sub set_exit_on_close { shift->{exit_on_close} = $_[1] }
sub set_selected_project_id { shift->{selected_project_id} = $_[1] }
sub set_selected_job_id { shift->{selected_job_id} = $_[1] }
sub set_selected_node_name { shift->{selected_node_name} = $_[1] }
sub set_master_event_queue { shift->{master_event_queue} = $_[1] }
sub set_log_watcher { shift->{log_watcher} = $_[1] }
sub set_event_timeout { shift->{event_timeout} = $_[1] }
sub set_node_gui { shift->{node_gui} = $_[1] }
sub set_cluster_ff { shift->{cluster_ff} = $_[1] }
sub set_pane1_pos { shift->{pane1_pos} = $_[1] }
sub set_pane2_pos { shift->{pane2_pos} = $_[1] }
sub set_pane3_pos { shift->{pane3_pos} = $_[1] }
sub set_window_height { shift->{window_height} = $_[1] }
sub set_window_width { shift->{window_width} = $_[1] }
sub selected_node {
my $self = shift;
my $name = $self->selected_node_name;
return unless defined $name;
$name = $name->[0];
return unless $self->master;
return $self->master->get_node_by_name($name);
}
sub selected_project {
my $self = shift;
my $id = $self->selected_project_id;
return unless defined $id;
$id = $id->[0];
return unless defined $id;
return unless $self->master;
return $self->master->get_project_by_id($id);
}
sub selected_job {
my $self = shift;
my $id = $self->selected_job_id;
return unless defined $id;
$id = $id->[0];
return unless defined $id;
return $self->selected_project->get_job_by_id($id);
}
# GUI Stuff ----------------------------------------------------------
sub get_gui_state_file {
return $ENV{HOME}."/.dvdrip/cluster-gui.state";
}
sub save_gui_state {
my $self = shift;
my $file = $self->get_gui_state_file;
open (my $fh, "> $file") or die "can't write $file";
print $fh $self->pane1_pos."\t".
$self->pane2_pos."\t".
$self->pane3_pos."\t".
$self->window_width."\t".
$self->window_height."\n";
close $fh;
1;
}
sub load_gui_state {
my $self = shift;
my $file = $self->get_gui_state_file;
if ( ! -f $file ) {
$self->set_pane1_pos(170);
$self->set_pane2_pos(180);
$self->set_pane2_pos(220);
$self->set_window_height(700);
$self->set_window_width(700);
$self->save_gui_state;
return 1;
}
open (my $fh, $file) or die "can't read $file";
my $line = <$fh>;
close $fh;
chomp $line;
my @pos = split("\t", $line);
$self->set_pane1_pos($pos[0]||170);
$self->set_pane2_pos($pos[1]||180);
$self->set_pane3_pos($pos[2]||220);
$self->set_window_width($pos[3]||700);
$self->set_window_height($pos[4]||700);
1;
}
sub open_window {
my $self = shift;
$self->load_gui_state;
my $context = $self->get_context;
$context->set_object( cluster_gui => $self );
my $cluster_ff = Gtk2::Ex::FormFactory->new(
context => $context,
sync => 1,
content => [
Gtk2::Ex::FormFactory::Window->new(
name => "cluster_window",
title => __ "dvd::rip - Cluster Control",
customize_hook => sub {
my ($gtk_window) = @_;
$_[0]->parent->set(
default_width => $self->window_width,
default_height => $self->window_height,
);
1;
},
closed_hook => sub {
$self->close_window;
},
content => [
$self->build_menu,
Gtk2::Ex::FormFactory::VBox->new (
object => "cluster",
expand => 1,
spacing => 0,
content => [
Gtk2::Ex::FormFactory::VPaned->new (
name => "cluster_vpane1",
attr => "cluster_gui.pane1_pos",
expand => 1,
content => [
$self->build_nodes_box,
Gtk2::Ex::FormFactory::VPaned->new (
name => "cluster_vpane2",
attr => "cluster_gui.pane2_pos",
content => [
$self->build_projects_box,
Gtk2::Ex::FormFactory::VPaned->new (
name => "cluster_vpane3",
attr => "cluster_gui.pane3_pos",
content => [
$self->build_jobs_box,
$self->build_log_box,
],
),
],
),
],
),
],
),
],
),
],
);
$self->set_cluster_ff($cluster_ff);
$self->set_form_factory($cluster_ff);
$cluster_ff->build;
$cluster_ff->update;
$cluster_ff->show;
1;
}
sub close_window {
my $self = shift;
$self->disconnect_master;
my $cluster_ff = $self->cluster_ff;
$cluster_ff->get_widget("cluster_vpane1")->widget_to_object;
$cluster_ff->get_widget("cluster_vpane2")->widget_to_object;
$cluster_ff->get_widget("cluster_vpane3")->widget_to_object;
my ($win_width, $win_height) =
$cluster_ff->get_widget("cluster_window")->get_gtk_parent_widget->get_size;
$self->set_window_height($win_height);
$self->set_window_width($win_width);
$self->save_gui_state;
$self->get_context->set_object( cluster_gui => undef );
$cluster_ff->close;
$self->set_cluster_ff(undef);
Gtk2->main_quit if $self->exit_on_close;
1;
}
sub build_menu {
my $self = shift;
return Gtk2::Ex::FormFactory::Menu->new (
menu_tree => [
__"_File" => {
item_type => '<Branch>',
children => [
__"_Close window" => {
item_type => '<StockItem>',
extra_data => 'gtk-close',
callback => sub { $self->close_window },
accelerator => '<ctrl>w',
},
],
},
__"_Master" => {
item_type => '<Branch>',
children => [
__"_Connect Master Daemon" => {
callback => sub { $self->connect_master },
item_type => '<StockItem>',
extra_data => 'gtk-connect',
accelerator => '<ctrl>M',
object => "!cluster",
},
__"_Disconnect from Master Daemon" => {
callback => sub { $self->disconnect_master },
item_type => '<StockItem>',
extra_data => 'gtk-disconnect',
accelerator => '<ctrl>D',
object => "cluster",
},
"sep" => {
item_type => "<Separator>",
},
__"Shutdown Master Daemon" => {
callback => sub { $self->shutdown_master },
item_type => '<StockItem>',
extra_data => 'gtk-media-stop',
object => "cluster",
},
],
},
__"_Node" => {
item_type => '<Branch>',
children => [
__"_Add node" => {
callback => sub { $self->add_node },
item_type => '<StockItem>',
extra_data => 'gtk-add',
object => "cluster",
},
__"_Edit node" => {
callback => sub { $self->edit_node },
item_type => '<StockItem>',
extra_data => 'gtk-edit',
accelerator => '<ctrl>N',
active_cond => sub {
my $node = $self->selected_node or return;
my $state = $node->state;
$node->state ne 'running';
},
active_depends => "cluster_node",
},
__"_Start node" => {
callback => sub { $self->start_node },
item_type => '<StockItem>',
extra_data => 'gtk-execute',
active_cond => sub {
my $node = $self->selected_node or return;
$node->state eq 'stopped';
},
active_depends => "cluster_node",
},
__"Sto_p node" => {
callback => sub { $self->stop_node },
item_type => '<StockItem>',
extra_data => 'gtk-cancel',
active_cond => sub {
my $node = $self->selected_node or return;
$node->state ne 'stopped';
},
active_depends => "cluster_node",
},
__"_Remove node" => {
callback => sub { $self->remove_node },
item_type => '<StockItem>',
extra_data => 'gtk-delete',
active_cond => sub {
my $node = $self->selected_node or return;
my $state = $node->state;
$node->state ne 'running';
},
active_depends => "cluster_node",
},
],
},
__"_Project" => {
item_type => '<Branch>',
children => [
__"_Edit project" => {
callback => sub { $self->edit_project },
item_type => '<StockItem>',
extra_data => 'gtk-edit',
active_cond => sub {
my $project = $self->selected_project or return;
$project->state eq 'not scheduled';
},
active_depends => "cluster_project",
},
__"_Start project" => {
callback => sub { $self->start_project },
item_type => '<StockItem>',
extra_data => 'gtk-execute',
active_cond => sub {
my $project = $self->selected_project or return;
$project->state eq 'not scheduled';
},
active_depends => "cluster_project",
},
__"_Cancel project" => {
callback => sub { $self->cancel_project },
item_type => '<StockItem>',
extra_data => 'gtk-cancel',
active_cond => sub {
my $project = $self->selected_project or return;
$project->state eq 'running';
},
active_depends => "cluster_project",
},
__"Res_tart project" => {
callback => sub { $self->restart_project },
item_type => '<StockItem>',
extra_data => 'gtk-redo',
active_cond => sub {
my $project = $self->selected_project or return;
$project->state eq 'cancelled';
},
active_depends => "cluster_project",
},
__"_Remove project" => {
callback => sub { $self->remove_project },
item_type => '<StockItem>',
extra_data => 'gtk-delete',
active_cond => sub {
my $project = $self->selected_project or return;
$project->state ne 'running';
},
active_depends => "cluster_project",
},
],
},
],
);
}
sub build_projects_box {
my $self = shift;
Gtk2::SimpleList->add_column_type(
'cluster_project_text',
type => "Glib::Scalar",
renderer => "Gtk2::CellRendererText",
attr => sub {
my ( $treecol, $cell, $model, $iter, $col_num ) = @_;
my $text = $model->get( $iter, $col_num );
my $state = $model->get( $iter, 3 );
my $run = $state eq 'running';
$cell->set( text => $text );
$cell->set( weight => $run ? 700 : 500 );
1;
},
);
return Gtk2::Ex::FormFactory::VBox->new(
title => __ "Project queue",
expand => 1,
content => [
Gtk2::Ex::FormFactory::List->new(
attr => "cluster.projects_list",
attr_select => "cluster_gui.selected_project_id",
attr_select_column => 0,
scrollbars => [ "automatic", "automatic" ],
expand => 1,
columns => [
"id",
__ "Number",
__ "Project",
__ "State",
__ "Progress",
],
types => [ "int", ("cluster_project_text") x 4 ],
selection_mode => "single",
customize_hook => sub {
my ($gtk_simple_list) = @_;
( $gtk_simple_list->get_columns )[0]->set( visible => 0 );
1;
},
),
Gtk2::Ex::FormFactory::HBox->new(
content => [
Gtk2::Ex::FormFactory::Button->new(
label => __ "Edit project",
stock => "gtk-edit",
clicked_hook => sub { $self->edit_project },
active_cond => sub {
my $project = $self->selected_project or return;
$project->state eq 'not scheduled';
},
active_depends => "cluster_project",
),
Gtk2::Ex::FormFactory::Button->new(
label => __ "Start project",
stock => "gtk-execute",
clicked_hook => sub { $self->start_project },
active_cond => sub {
my $project = $self->selected_project or return;
$project->state eq 'not scheduled';
},
active_depends => "cluster_project",
),
Gtk2::Ex::FormFactory::Button->new(
label => __"Cancel project",
stock => "gtk-cancel",
clicked_hook => sub { $self->cancel_project },
active_cond => sub {
my $project = $self->selected_project or return;
$project->state eq 'running';
},
active_depends => "cluster_project",
),
Gtk2::Ex::FormFactory::Button->new(
label => __"Restart project",
stock => "gtk-redo",
clicked_hook => sub { $self->restart_project },
active_cond => sub {
my $project = $self->selected_project or return;
$project->state eq 'cancelled' ||
$project->state eq 'error';
},
active_depends => "cluster_project",
),
Gtk2::Ex::FormFactory::Button->new(
label => __ "Remove project",
stock => "gtk-delete",
clicked_hook => sub { $self->remove_project },
active_cond => sub {
my $project = $self->selected_project or return;
$project->state ne 'running';
},
active_depends => "cluster_project",
),
],
),
],
);
}
sub build_jobs_box {
my $self = shift;
return Gtk2::Ex::FormFactory::VBox->new(
title => __"Job running status",
expand => 1,
content => [
Gtk2::Ex::FormFactory::ExecFlow->new(
name => "cluster_exec_flow",
attr => "cluster.exec_flow_job",
scrollbars => [ 'automatic', 'automatic' ],
expand => 1,
customize_hook => sub {
my ($tree_view) = @_;
$tree_view->signal_connect (
row_activated => sub {
my ($gtk_tree_view, $path) = @_;
my $model = $gtk_tree_view->get_model;
my $iter = $model->get_iter($path);
my $job_id = $model->get($iter, 2);
my $job = $self->master->get_job_from_id($job_id);
return 1 unless $job->get_error_message;
$self->long_message_window (
message => $job->get_error_message,
);
1;
}
);
},
),
],
);
}
sub jobs_list {
my $self = shift;
my $project = $self->selected_project or return;
return $project->jobs_list;
}
sub build_nodes_box {
my $self = shift;
Gtk2::SimpleList->add_column_type(
'cluster_node_text',
type => "Glib::Scalar",
renderer => "Gtk2::CellRendererText",
attr => sub {
my ( $treecol, $cell, $model, $iter, $col_num ) = @_;
my $text = $model->get( $iter, $col_num );
my $state = $model->get( $iter, 4 );
my $run = $state !~ /stopped|idle|offline/;
$cell->set( text => $text );
$cell->set( weight => $run ? 700 : 500 );
1;
},
);
return Gtk2::Ex::FormFactory::VBox->new(
title => __ "Registered Nodes",
expand => 1,
content => [
Gtk2::Ex::FormFactory::List->new(
attr => "cluster.nodes_list",
attr_select => "cluster_gui.selected_node_name",
attr_select_column => 0,
expand => 1,
height => 100,
scrollbars => [ "automatic", "automatic" ],
columns => [
"name",
__ "Number",
__ "Name",
__ "Job",
__ "Progress"
],
# types => [
# "int", ("cluster_node_text") x 4
# ],
selection_mode => "single",
customize_hook => sub {
my ($gtk_simple_list) = @_;
( $gtk_simple_list->get_columns )[0]->set( visible => 0 );
1;
},
),
Gtk2::Ex::FormFactory::HBox->new(
content => [
Gtk2::Ex::FormFactory::Button->new(
object => "cluster",
label => __ "Add node",
stock => "gtk-add",
clicked_hook => sub { $self->add_node },
),
Gtk2::Ex::FormFactory::Button->new(
object => "cluster_node",
label => __ "Edit node",
stock => "gtk-edit",
clicked_hook => sub { $self->edit_node },
active_cond => sub {
my $node = $self->selected_node or return;
my $state = $node->state;
$node->state ne 'running';
},
active_depends => "cluster_node",
),
Gtk2::Ex::FormFactory::Button->new(
object => "cluster_node",
label => __ "Start node",
stock => "gtk-execute",
clicked_hook => sub { $self->start_node },
active_cond => sub {
my $node = $self->selected_node or return;
$node->state eq 'stopped';
},
active_depends => "cluster_node",
),
Gtk2::Ex::FormFactory::Button->new(
object => "cluster_node",
label => __ "Stop node",
stock => "gtk-cancel",
clicked_hook => sub { $self->stop_node },
active_cond => sub {
my $node = $self->selected_node or return;
$node->state ne 'stopped';
},
active_depends => "cluster_node",
),
],
),
],
);
}
sub build_log_box {
my $self = shift;
return Gtk2::Ex::FormFactory::VBox->new(
title => __ "Cluster control daemon log output",
height => 80,
expand => 1,
content => [
Gtk2::Ex::FormFactory::TextView->new(
scrollbars => [ "never", "always" ],
expand => 1,
properties => {
editable => 0,
cursor_visible => 0,
wrap_mode => "word",
},
customize_hook => sub {
my ($gtk_text_view) = @_;
my $font = Gtk2::Pango::FontDescription->from_string(
"mono 7.2");
$gtk_text_view->modify_font($font);
my $tag_table = Gtk2::TextTagTable->new;
$tag_table->add(
$self->create_text_tag(
"date", foreground => "#666666",
)
);
my $buffer = Gtk2::TextBuffer->new($tag_table);
$gtk_text_view->set_buffer($buffer);
$self->set_gtk_log_buffer($buffer);
$self->set_gtk_log_view($gtk_text_view);
1;
},
),
],
);
}
sub connect_master {
my $self = shift;
return if $self->master;
if ( !$self->config('cluster_master_local') &&
!($self->config('cluster_master_server') &&
$self->config('cluster_master_port') ) ) {
$self->message_window (
message => __("You must first configure a ".
"cluster control daemon\n".
"in the Preferences dialog."),
);
return;
}
my $server = $self->config('cluster_master_local')
? 'localhost'
: $self->config('cluster_master_server');
my $port = $self->config('cluster_master_port');
my $rpc_client = Event::RPC::Client->new(
host => $server,
port => $port,
error_cb => sub { $self->client_server_error },
class_map => {
'Event::ExecFlow::Job' => 'dvd_rip::Event::ExecFlow::Job',
'Event::ExecFlow::Job::Group' => 'dvd_rip::Event::ExecFlow::Job::Group',
'Event::ExecFlow::Job::Command' => 'dvd_rip::Event::ExecFlow::Job::Command',
'Event::ExecFlow::Job::Code' => 'dvd_rip::Event::ExecFlow::Job::Code',
},
);
eval { $rpc_client->connect };
if ( not $rpc_client->get_connected ) {
if ( not $self->config('cluster_master_local') ) {
$self->error_window(
message => __x(
"Can't connect to master daemon on {server}:{port}.",
server => $server,
port => $port
)
);
return;
}
# Ok, we try to start a local master daemon
system("dvdrip-master 3 >/dev/null 2>&1 &");
sleep 1;
eval { $rpc_client->connect };
# give up, if we still have no connection
if ( not $rpc_client->get_connected ) {
$self->error_window(
message => __x(
"Can't start local master daemon on port {port}.\n".
"Execute the dvdrip-master program by hand to\n".
"see why it doesn't run.",
port => $port
)
);
return;
}
}
my $master = Video::DVDRip::Cluster::Master->get_master();
$master->hello;
$self->set_rpc_client($rpc_client);
$self->set_master($master);
$self->get_context->set_object( cluster => $master );
$self->get_context->set_object( "!cluster" => undef );
$self->get_context->update_object_widgets("cluster_node");
$self->get_context->update_object_widgets("cluster_project");
my $sock = Event::RPC::Client->log_connect(
server => $server,
port => $port + 1,
);
$self->set_log_socket($sock);
$self->set_master_event_queue( [] );
my $log_buffer = "";
my $log_watcher = Gtk2::Helper->add_watch(
$sock->fileno,
'in',
sub {
return 1 unless $self->master;
if ( !sysread( $sock, $log_buffer, 4096, length($log_buffer) ) ) {
$self->client_server_error;
return 1;
}
my $buffer_offset;
while ( $log_buffer =~ /(.*\n)/mg ) {
my $line = $1;
chomp($line);
$buffer_offset = pos($log_buffer);
if ( $line =~ /EVENT\t(.*)/ ) {
$self->enqueue_master_event( split( "\t", $1 ) );
}
else {
my $buffer = $self->gtk_log_buffer;
$buffer->insert( $buffer->get_end_iter, $line . "\n" );
}
}
$log_buffer = substr($log_buffer, $buffer_offset);
1;
}
);
my $event_timeout = Glib::Timeout->add(
200,
sub {
return unless $self->master;
$self->process_master_event_queue;
1;
}
);
$self->set_log_watcher($log_watcher);
$self->set_event_timeout($event_timeout);
1;
}
sub disconnect_master {
my $self = shift;
#-- remove all timeouts, sockets and watchers
Glib::Source->remove( $self->event_timeout )
if $self->event_timeout;
close($self->log_socket)
if $self->log_socket;
Gtk2::Helper->remove_watch( $self->log_watcher )
if $self->log_watcher;
#-- disconnect client
my $rpc_client = $self->rpc_client;
$rpc_client->disconnect if $rpc_client;
#-- destroy all objects which are connected to the master
$self->set_master(undef);
$self->set_log_watcher(undef);
$self->set_event_timeout(undef);
$self->set_log_socket(undef);
$self->set_rpc_client(undef);
$self->set_selected_project_id(undef);
$self->set_selected_node_name(undef);
#-- now (and not earlier!) destroy the objects in the context.
#-- if that would happen bevor undef'ing the attributes above
#-- FormFactory will trigger actions on the line again, which
#-- is broken already!
$self->get_context->set_object( cluster_project => undef );
$self->get_context->set_object( cluster_node => undef );
$self->get_context->set_object( cluster => undef );
$self->get_context->set_object( "!cluster" => 1 );
#-- close windows which are probably open
my $cluster_node_gui =
$self->get_context->get_object("cluster_node_gui");
my $cluster_title_gui =
$self->get_context->get_object("cluster_title_gui");
$cluster_node_gui->close_window() if $cluster_node_gui;
$cluster_title_gui->close_window() if $cluster_title_gui;
1;
}
sub enqueue_master_event {
my $self = shift;
my ( $event, @args ) = @_;
my $queue = $self->master_event_queue;
push @{$queue}, [ $event, \@args ];
return;
}
{
my %event2action = (
PROJECT_UPDATE => "update_projects_list",
PROJECT_LIST_UPDATE => "update_projects_list",
PROJECT_DELETED => "update_projects_list",
JOB_PLAN_UPDATE => "update_jobs_list[ARG0]",
JOB_UPDATE => "update_job[ARG0]",
JOB_ADDED => "update_job_add[ARG0]",
JOB_REMOVED => "update_job_removed[ARG0]",
NODE_UPDATE => "update_nodes_list",
NODE_DELETED => "update_nodes_list",
JOB_PROGRESS_UPDATE => "update_job_progress[ARG0,ARG1,ARG2,ARG3]",
NODE_PROGRESS_UPDATE => "update_node_progress[ARG0,ARG1,ARG2]",
NODE_TEST_FINISHED => "node_test_finished",
NO_MASTER_NODE_FOUND => "no_master_node_found",
);
sub process_master_event_queue {
my $self = shift;
my $queue = $self->master_event_queue;
return unless $self->master;
return if @{$queue} == 0;
my $cluster_ff = $self->cluster_ff;
my %actions_seen;
my @actions;
for ( my $i = @{$queue} - 1; $i >= 0; --$i ) {
my $action = $event2action{ $queue->[$i]->[0] };
if ( !$action ) {
warn "Unknown master event $queue->[$i]->[0]";
next;
}
if ( $action =~ /ARG/ ) {
$action =~ s/ARG0/$queue->[$i]->[1]->[0]/;
$action =~ s/ARG1/$queue->[$i]->[1]->[1]/;
$action =~ s/ARG2/$queue->[$i]->[1]->[2]/;
$action =~ s/ARG3/$queue->[$i]->[1]->[3]/;
}
next if $actions_seen{$action};
$actions_seen{$action} = 1;
unshift @actions, [ $action, $queue->[$i] ];
}
# print "Queue items: ".@{$queue}." => actions ".@actions."\n";
@{$queue} = ();
my $context = $self->get_context;
my $node_slist
= $self->cluster_ff->get_widget("cluster.nodes_list")
->get_gtk_widget;
foreach my $action_item (@actions) {
my $action = $action_item->[0];
my $event = $action_item->[1]->[0];
my $args = $action_item->[1]->[1];
if ( $action eq 'update_projects_list' ) {
$context->update_object_attr_widgets("cluster.projects_list");
$context->update_object_widgets("cluster_project");
}
elsif ( $action =~ /^update_jobs_list/ ) {
my ($project_id) = @{$args};
next unless $self->selected_project_id;
# print "update_jobs_list: $project_id <> ".$self->selected_project_id->[0]."\n";
$context->update_object_attr_widgets("cluster_gui.jobs_list")
if $project_id == $self->selected_project_id->[0];
}
elsif ( $action =~ /^update_job_progress/ ) {
next unless $self->selected_project_id;
my ( $project_id, $job_nr, $state, $progress ) = @{$args};
next if $project_id != $self->selected_project_id->[0];
# $job_slist->{data}->[ $job_nr - 1 ]->[4] = $state;
# $job_slist->{data}->[ $job_nr - 1 ]->[5] = $progress;
}
elsif ( $action =~ /^update_node_progress/ ) {
my ( $name, $job, $progress ) = @{$args};
for ( my $i = 0; $i < @{ $node_slist->{data} }; ++$i ) {
if ( $node_slist->{data}->[$i]->[0] eq $name ) {
$node_slist->{data}->[$i]->[3] = $job;
$node_slist->{data}->[$i]->[4] = $progress;
last;
}
}
}
elsif ( $action =~ /^update_job_add/ ) {
my ($job_id) = @{$args};
my $job = $self->master->get_job_from_id($job_id);
$cluster_ff->get_widget("cluster_exec_flow")->add_job($job)
if $job;
}
elsif ( $action =~ /^update_job_removed/ ) {
my ($job_id) = @{$args};
$cluster_ff->get_widget("cluster_exec_flow")->remove_job_with_id($job_id);
}
elsif ( $action =~ /^update_job/ ) {
my ($job_id) = @{$args};
my $job = $self->master->get_job_from_id($job_id);
$cluster_ff->get_widget("cluster_exec_flow")->update_job($job)
if $job;
}
elsif ( $action eq 'update_nodes_list' ) {
$context->update_object_attr_widgets("cluster.nodes_list");
$context->update_object_attr_widgets("cluster_node");
}
elsif ( $action eq 'no_master_node_found' ) {
$self->error_window(
message => __ "Please configure the master node first",
);
my $node_gui = $self->node_gui;
$node_gui->stop_node_test_progress if $node_gui;
}
elsif ( $action eq 'node_test_finished' ) {
my $node_gui = $self->node_gui;
$node_gui->node_test_finished if $node_gui;
}
}
1;
}
}
sub client_server_error {
my $self = shift;
return unless $self->master;
$self->error_window (
message => __"Connection to master daemon lost!",
);
$self->disconnect_master;
1;
}
sub add_project {
my $self = shift;
my %par = @_;
my ( $project, $title ) = @par{ 'project', 'title' };
my $cluster_project = Video::DVDRip::Cluster::Project->new(
project => $project,
title_nr => $title->nr,
);
$self->master->add_project( project => $cluster_project, );
$self->edit_project(
project => $cluster_project,
just_added => 1,
);
1;
}
sub edit_project {
my $self = shift;
my %par = @_;
my ($project, $just_added) = @par{'project','just_added'};
$project ||= $self->selected_project;
return 1 if not $project;
Video::DVDRip::GUI::Cluster::Title->new(
cluster_ff => $self->cluster_ff,
master => $self->master,
title => $project->title,
just_added => $just_added,
)->open_window;
1;
}
sub remove_project {
my $self = shift;
my $project = $self->selected_project;
return if not $project;
$self->confirm_window(
message => __"Do you want to remove the selected project?",
yes_callback => sub {
return unless $self->master;
return if $project->state eq 'running';
$self->master->remove_project( project => $project );
},
);
1;
}
sub add_node {
my $self = shift;
my $node_gui = Video::DVDRip::GUI::Cluster::Node->new(
cluster_ff => $self->cluster_ff,
master => $self->master,
node => Video::DVDRip::Cluster::Node->new,
just_added => 1,
);
$node_gui->open_window;
$self->set_node_gui($node_gui);
1;
}
sub edit_node {
my $self = shift;
my $node = $self->selected_node;
return 1 if not $node;
return 1 if $node->state eq 'running';
my $node_gui = Video::DVDRip::GUI::Cluster::Node->new(
cluster_ff => $self->cluster_ff,
master => $self->master,
node => $node,
);
$node_gui->open_window;
$self->set_node_gui($node_gui);
1;
}
sub stop_node {
my $self = shift;
my $node = $self->selected_node;
return 1 if not $node;
return 1 if $node->state eq 'stopped';
$node->stop;
1;
}
sub start_node {
my $self = shift;
my $node = $self->selected_node;
return 1 if not $node;
return 1
if $node->state ne 'stopped'
and $node->state ne 'aborted';
$node->start;
1;
}
sub remove_node {
my $self = shift;
my $node = $self->selected_node;
return 1 if not $node;
return 1 if $node->state eq 'running';
$self->confirm_window(
message => __ "Do you want to remove the selected node?",
yes_callback => sub {
return unless $self->master;
$self->master->remove_node( node => $self->selected_node );
},
);
1;
}
sub select_job {
my $self = shift;
my ( $widget, $row ) = @_;
$self->set_selected_job_row($row);
$self->set_selected_job_id( $self->gtk_widgets->{job_clist_ids}->[$row] );
1;
}
sub select_node {
my $self = shift;
my ( $widget, $row ) = @_;
$self->set_selected_node_row($row);
$self->set_selected_node( $self->master->nodes->[$row] );
1;
}
sub start_project {
my $self = shift;
my $project = $self->selected_project;
return if not $project;
return if $project->state ne 'not scheduled';
$self->master->schedule_project( project => $project );
1;
}
sub cancel_project {
my $self = shift;
my $project = $self->selected_project;
return if not $project;
return if $project->state ne 'running';
$self->master->cancel_project( project => $project );
1;
}
sub restart_project {
my $self = shift;
my $project = $self->selected_project;
return if not $project;
$self->master->restart_project( project => $project );
1;
}
sub shutdown_master {
my $self = shift;
$self->confirm_window(
message => __
"Do you really want to shutdown\nthe Cluster Control Daemon?",
yes_callback => sub {
return unless $self->master;
$self->master->shutdown;
$self->disconnect_master;
1;
},
);
1;
}
1;