File Coverage

blib/lib/WWW/Slides/Talk.pm
Criterion Covered Total %
statement 54 227 23.7
branch 0 54 0.0
condition 0 5 0.0
subroutine 18 42 42.8
pod 0 21 0.0
total 72 349 20.6


line stmt bran cond sub pod time code
1             package WWW::Slides::Talk;
2             {
3 1     1   4985 use warnings;
  1         2  
  1         36  
4              
5             # use diagnostics;
6 1     1   5 use strict;
  1         2  
  1         29  
7 1     1   5 use Carp;
  1         2  
  1         64  
8 1     1   5 use version; our $VERSION = qv('0.0.5');
  1         2  
  1         6  
9 1     1   1532 use Object::InsideOut;
  1         74787  
  1         6  
10 1     1   1187 use IO::Socket;
  1         30260  
  1         19  
11 1     1   1481 use IO::Select;
  1         1680  
  1         51  
12 1     1   896 use English qw( -no_match_vars );
  1         2525  
  1         6  
13              
14 1     1   1073 use WWW::Slides::Attendee;
  1         4  
  1         11  
15 1     1   70 use WWW::Slides::SlideTracker;
  1         3  
  1         5  
16              
17             # Other recommended modules (uncomment to use):
18             # use IO::Prompt;
19             # use Perl6::Export;
20             # use Perl6::Slurp;
21             # use Perl6::Say;
22             # use Regexp::Autoflags;
23             # use Readonly;
24              
25             #--------------------------------------------------------------------
26             #
27             # Member variables and initialisation
28             #
29             my @controller : Field # Entry point for talk control
30             : Std(Name => 'controller')
31             : Arg(Name => 'controller', Mandatory => 1);
32              
33             my @door : Field # Entry point for talk "listening"
34             : Std(Name => 'door', Private => 1);
35             my @port : Field # Port used for talk TCP socket
36             : Std(Name => 'port', Private => 1)
37             : Arg(Name => 'port', Mandatory => 1);
38             my @attendees : Field # List of registered attendees
39             : Std(Name => 'attendees', Private => 1);
40             my @must_book : Field # Should attendee book before entering?
41             : Std(Name => 'must_book', Private => 1) : Get(Name => 'must_book')
42             : Arg(Name => 'must_book', Default => 0);
43             my @booked : Field # Track expected attendees
44             : Std(Name => 'booked', Private => 1)
45             : Get(Name => 'booked', Private => 1);
46             my @accepts_detaches : Field # Attendees can detach
47             : Std(Name => 'accepts_detaches', Private => 1)
48             : Get(Name => 'accepts_detaches')
49             : Arg(Name => 'accepts_detaches', Default => 1);
50              
51             my @slide_show : Field # Where we're getting the slides from
52             : Std(Name => 'slide_show', Private => 1)
53             : Get(Name => 'slide_show', Private => 1)
54             : Arg(Name => 'slide_show', Mandatory => 1);
55             my @tracker : Field # Track current slide
56             : Std(Name => 'tracker', Private => 1)
57             : Get(Name => 'tracker', Private => 1);
58              
59             my @ping_interval : Field # Anti-timeout
60             : Std(Name => 'ping_interval')
61             : Arg(Name => 'ping_interval', Default => 60);
62             my @alive : Field # Is this talk still alive?
63             : Std(Name => 'alive', Private => 1)
64             : Get(Name => 'is_alive', Private => 1);
65             my @selector : Field # For select() operations
66             : Std(Name => 'selector', Private => 1);
67              
68             my @logger : Field # Where to send any log message
69             : Std(Name => 'logger', Private => 1) : Get(Name => 'logger')
70             : Arg(Name => 'logger');
71              
72             sub _init : Init {
73 0         0 my $self = shift;
74 0         0 my ($args) = @_;
75              
76             # Ensure there's a logger, even a fake one
77 0 0       0 if (! $self->logger()) {
78 0         0 require WWW::Slides::BasicLogger;
79 0         0 $self->set_logger(WWW::Slides::BasicLogger->new(fake => 1));
80             }
81              
82 0 0       0 my $door = IO::Socket::INET->new(
83             Proto => 'tcp',
84             LocalPort => $self->get_port(),
85             ReuseAddr => 1,
86             Listen => 3,
87             )
88             or croak "could not create door socket on port ",
89             $self->get_port();
90 0         0 $self->set_door($door);
91              
92 0         0 my $selector = IO::Select->new($door);
93 0         0 $self->set_selector($selector);
94              
95 0         0 my $controller = $self->get_controller();
96 0         0 $controller->set_selector($selector);
97              
98 0         0 $self->set_attendees({}); # Empty talk at the beginning
99 0 0       0 $self->set_booked({}) if $self->must_book();
100              
101 0         0 $self->set_tracker( # Auto-places on the first slide
102             WWW::Slides::SlideTracker->new(
103             slide_show => $self->get_slide_show()
104             )
105             );
106              
107 0         0 $self->set_alive(1);
108              
109              
110 0         0 return;
111 1     1   523 } ## end sub _init :
  1         1  
  1         4  
112             #---------------------------------------------------------------------
113              
114             sub run { # main loop function
115 0     0 0   my $self = shift;
116 0           my $door = $self->get_door();
117 0           my $selector = $self->get_selector();
118 0           my $controller = $self->get_controller();
119              
120 0           $self->logger()->info('run(): entering main loop');
121 0   0       while ($self->is_alive() && $controller->is_alive()) {
122 0           my @ready = $selector->can_read($self->get_ping_interval());
123 0           $self->ping();
124              
125 0           for my $fh (@ready) { # Do all the work
126 0 0         if ($controller->owns($fh)) {
    0          
127 0           $self->execute_commands($fh);
128             }
129             elsif ($fh == $door) {
130 0           $self->welcome_attendee();
131             }
132             else {
133 0           $self->check_attendee($self->get_attendee($fh));
134             }
135             } ## end for my $fh (@ready)
136             } ## end while ($self->is_alive() ...
137              
138 0 0         $self->logger()->info('run() exited from loop, talk ',
    0          
139             ($self->is_alive() ? 'alive' : 'dead'),
140             ', controller ',
141             ($controller->is_alive() ? 'alive' : 'dead')
142             );
143 0           $self->cleanup();
144              
145 0           return;
146             } ## end sub run
147              
148             sub cleanup : Private {
149 0         0 my $self = shift;
150 0         0 $self->logger()->debug('cleanup()');
151 0         0 $self->remove_attendee($_) for $self->get_all_attendees();
152 0         0 return;
153 1     1   533 }
  1         2  
  1         4  
154              
155             # Attendees selection
156             sub get_attached_attendees {
157 0     0 0   my $self = shift;
158 0           return grep { $_->is_attached() } $self->get_all_attendees();
  0            
159             }
160              
161             sub get_all_attendees {
162 0     0 0   my $self = shift;
163 0           return values %{$self->get_attendees()};
  0            
164             }
165              
166             sub resolve_attendees {
167 0     0 0   my $self = shift;
168 0 0         return map { ref $_ ? $_ : $self->get_attendee($_) } @_;
  0            
169             }
170              
171             sub get_attendee {
172 0     0 0   my $self = shift;
173 0           my ($id) = @_;
174              
175 0           my $attendees = $self->get_attendees();
176 0 0         if (ref $id) { # It's a filehandle
177 0 0         die "unexpected attendee's filehandle [$id]"
178             unless exists $attendees->{$id};
179 0           return $attendees->{$id};
180             }
181              
182             # It's an external identifier
183 0           for my $attendee (values %$attendees) {
184 0 0         return $attendee if $attendee->id() eq $id;
185             }
186 0           die "could not find id $id";
187             } ## end sub get_attendee
188              
189             # Content sending management
190             sub broadcast : Private {
191 0         0 my $self = shift;
192 0         0 my $command = shift;
193 0 0       0 my @attendees = @_
194             ? $self->resolve_attendees(@_)
195             : $self->get_all_attendees();
196              
197             # Ensure that $command is a reference to a sub
198 0 0 0     0 if ((ref($command) || '') ne 'CODE') { # Auto-define sub
199 0 0       0 my ($cmd, @args) = ref $command ? @$command : $command;
200             $command = sub {
201 0         0 my $attendee = shift;
202 0         0 my $method = $attendee->can($cmd);
203 0 0       0 $attendee->$method(@args) if $method;
204 0         0 return;
205 0         0 };
206             } ## end if ((ref($command) || ...
207              
208 0         0 return map { $command->($_) } @attendees;
  0         0  
209 1     1   577 } ## end sub broadcast :
  1         2  
  1         4  
210              
211             sub ping { # Remind them all to remain awake
212 0     0 0   return shift->broadcast('ping');
213             }
214              
215             sub get_show_attendees {
216 0     0 0   my $self = shift;
217 0 0         return @_ if @_;
218 0           return $self->get_attached_attendees();
219             }
220              
221             # Transition commands, default to attached attendees
222             sub show { # Set specific slide
223 0     0 0   my $self = shift;
224 0           my $slide_no = shift;
225              
226 0 0         if (! $self->slide_show()->validate_slide_id($slide_no)) {
227 0           $self->logger()->error("show(): invalid slide $slide_no");
228 0           return;
229             }
230            
231 0 0         if (@_) {
232 0           $self->logger()->debug("show(): going to slide $slide_no "
233             . 'for some attendees only');
234             }
235             else {
236 0           $self->logger()->debug("show(): going to slide $slide_no");
237 0           $self->tracker()->goto($slide_no);
238             }
239            
240 0 0         if (my @attendees = $self->get_show_attendees(@_)) {
241 0           return $self->broadcast(['show', $slide_no], @attendees);
242             }
243 0           $self->logger()->debug('show(): no attendee found');
244 0           return;
245             } ## end sub show
246              
247             sub constrained_show : Private { # Factorised transition
248 0         0 my $self = shift;
249 0         0 my $bare = shift;
250 0         0 my $method = 'show_' . $bare;
251              
252 0 0       0 if (@_) {
253 0         0 $self->logger()->debug($method . '() for some attendees');
254             }
255             else {
256 0         0 my $tracker = $self->tracker();
257 0         0 $tracker->can('goto_' . $bare)->($tracker); # Call method OO-style
258 0         0 $self->logger()->debug($method . '()');
259             }
260            
261 0 0       0 if (my @attendees = $self->get_show_attendees(@_)) {
262 0         0 return $self->broadcast($method, @attendees);
263             }
264 0         0 $self->logger()->debug($method . '(): no attendee found');
265 0         0 return;
266 1     1   571 }
  1         2  
  1         5  
267              
268             sub show_first {
269 0     0 0   my $self = shift;
270 0           return $self->constrained_show('first', @_);
271             } ## end sub show_first
272              
273             sub show_last {
274 0     0 0   my $self = shift;
275 0           return $self->constrained_show('last', @_);
276             } ## end sub show_last
277              
278             sub show_next {
279 0     0 0   my $self = shift;
280 0           return $self->constrained_show('next', @_);
281             } ## end sub show_next
282              
283             sub show_previous {
284 0     0 0   my $self = shift;
285 0           return $self->constrained_show('previous', @_);
286             } ## end sub show_previous
287              
288             # Command execution
289             sub execute_commands : Private {
290 0         0 my ($self, $fh) = @_;
291 0         0 $self->get_controller()->execute_commands($fh, $self);
292 0         0 return;
293 1     1   327 }
  1         2  
  1         9  
294              
295             sub welcome_attendee : Private {
296 0         0 my $self = shift;
297 0         0 my $logger = $self->logger();
298              
299 0         0 $logger->debug('welcome_attendee(): accept()-ing new attendee');
300 0 0       0 my $handle = $self->get_door()->accept()
301             or croak("accept(): $OS_ERROR");
302 0         0 $logger->debug("welcome_attendee(): $handle");
303              
304             # This also serves the first slide to the connected user
305 0         0 $logger->debug('welcome_attendee(): creating object for new attendee');
306 0         0 my $attendee = WWW::Slides::Attendee->new(
307             handle => $handle,
308             slide_show => scalar $self->get_slide_show(),
309             current_slide => scalar $self->tracker->current(),
310             check_booking => $self->must_book(),
311             );
312              
313             # Register attendee
314 0         0 $self->get_attendees()->{$handle} = $attendee;
315 0         0 $self->get_selector()->add($handle);
316              
317 0         0 return;
318 1     1   352 } ## end sub welcome_attendee :
  1         2  
  1         4  
319              
320             sub remove_attendee : Private {
321 0         0 my $self = shift;
322 0         0 my ($attendee) = @_;
323 0         0 my $handle = $attendee->get_handle();
324              
325 0         0 $self->logger()->debug('removing attendee');
326 0         0 $self->get_selector()->remove($handle);
327 0         0 delete $self->get_attendees()->{$handle};
328              
329 0         0 $attendee->shut_down();
330 0         0 return;
331 1     1   259 } ## end sub remove_attendee :
  1         1  
  1         5  
332              
333             sub check_attendee : Private {
334 0           my $self = shift;
335 0           my ($attendee) = @_;
336              
337 0           my $is_alive;
338 0 0         if ($attendee->must_check_booking()) {
339 0           my $code = $attendee->booking_code();
340 0 0         $is_alive = delete($self->booked()->{$code}) if defined $code;
341 0 0         $attendee->book_ok() if $is_alive;
342             }
343             else {
344 0           $is_alive = $attendee->handle_input();
345             }
346              
347 0 0         $self->remove_attendee($attendee) unless $is_alive;
348 0           return;
349 1     1   280 } ## end sub check_attendee :
  1         2  
  1         5  
350              
351             sub book {
352 0     0 0   my $self = shift;
353 0           my ($code) = @_;
354 0           $self->logger()->debug("book(): booking with code $code");
355 0           $self->booked()->{$code} = 1;
356 0           return;
357             } ## end sub book
358              
359             sub quit {
360 0     0 0   my $self = shift;
361 0           $self->logger()->debug("quit() requested");
362 0           $self->set_alive(0);
363 0           return;
364             }
365              
366             sub attach {
367 0     0 0   my $self = shift;
368 0           my $current = $self->tracker()->current();
369 0           $self->logger()->debug("attach()");
370             return $self->broadcast(
371             sub {
372 0     0     my $attendee = shift;
373 0           $attendee->attach();
374 0           $attendee->show($current);
375 0           return;
376             },
377             @_
378 0           );
379              
380 0           return;
381             } ## end sub attach
382              
383             sub detach {
384 0     0 0   my $self = shift;
385 0 0         return unless $self->accepts_detaches();
386 0           $self->logger()->debug("detach()");
387             return $self->broadcast(
388             sub {
389 0     0     shift->detach();
390 0           return;
391             },
392             @_
393 0           );
394             } ## end sub detach
395              
396             sub clamp {
397 0     0 0   my $self = shift;
398 0           $self->logger()->debug("clamp()");
399 0           $self->set_accepts_detaches(0);
400 0           $self->attach();
401 0           return;
402             } ## end sub clamp
403              
404             sub loose {
405 0     0 0   my $self = shift;
406 0           $self->logger()->debug("loose()");
407 0           $self->set_accepts_detaches(1);
408 0           return;
409             }
410              
411 0     0 0   sub get_current { return shift->tracker()->current(); }
412 0     0 0   sub get_total { return shift->slide_show()->id_last(); }
413              
414             sub get_attendees_details {
415 0     0 0   my $self = shift;
416             return $self->broadcast(
417             sub {
418 0     0     my $attendee = shift;
419             return {
420 0           is_attached => $attendee->is_attached(),
421             id => $attendee->id(),
422             current_slide => $attendee->tracker()->current(),
423             peer_address => $attendee->peer_address(),
424             };
425             },
426             @_
427 0           );
428             } ## end sub get_attendees_details
429             }
430              
431             1; # Magic true value required at end of module
432             __END__