File Coverage

blib/lib/IO/Iron/IronWorker/Client.pm
Criterion Covered Total %
statement 60 276 21.7
branch 3 44 6.8
condition n/a
subroutine 14 29 48.2
pod 15 15 100.0
total 92 364 25.2


line stmt bran cond sub pod time code
1             package IO::Iron::IronWorker::Client;
2              
3             ## no critic (Documentation::RequirePodAtEnd
4             ## no critic (Documentation::RequirePodSections)
5             ## no critic (ControlStructures::ProhibitPostfixControls)
6             ## no critic (Subroutines::RequireArgUnpacking)
7              
8 4     4   107805 use 5.010_000;
  4         24  
9 4     4   20 use strict;
  4         8  
  4         99  
10 4     4   20 use warnings;
  4         7  
  4         143  
11              
12             # Global creator
13             BEGIN {
14 4     4   920 use parent qw( IO::Iron::ClientBase ); # Inheritance
  4         693  
  4         21  
15             }
16              
17             # Global destructor
18       4     END {
19             }
20              
21             # ABSTRACT: IronWorker (Online Worker Platform) Client.
22              
23             our $VERSION = '0.14'; # VERSION: generated by DZP::OurPkgVersion
24              
25 4     4   353 use Log::Any qw{$log};
  4         10  
  4         22  
26 4     4   848 use Hash::Util 0.06 qw{lock_keys lock_keys_plus unlock_keys legal_keys};
  4         76  
  4         21  
27 4     4   287 use Carp::Assert::More;
  4         23  
  4         835  
28 4     4   73 use English '-no_match_vars';
  4         31  
  4         37  
29 4     4   2621 use Params::Validate qw(:all);
  4         19279  
  4         694  
30              
31 4     4   2085 use IO::Iron::IronWorker::Api ();
  4         15  
  4         112  
32 4     4   939 use IO::Iron::Common ();
  4         20  
  4         147  
33             require IO::Iron::Connection;
34             require IO::Iron::IronWorker::Task;
35              
36             # CONSTANTS for this package
37              
38             # DEFAULTS
39 4     4   966 use Const::Fast;
  4         5001  
  4         23  
40              
41             # Service specific!
42             const my $DEFAULT_API_VERSION => '2';
43             const my $DEFAULT_HOST => 'worker-aws-us-east-1.iron.io';
44              
45             sub new {
46 1     1 1 2960 my $class = shift;
47             my %params = validate(
48             @_,
49             {
50 1         6 map { $_ => { type => SCALAR, optional => 1 }, } IO::Iron::Common::IRON_CLIENT_PARAMETERS(), ## no critic (ValuesAndExpressions::ProhibitCommaSeparatedStatements)
  10         64  
51             }
52             );
53              
54 1         22 $log->tracef( 'Entering new(%s, %s)', $class, \%params );
55 1         12 my $self = IO::Iron::ClientBase->new();
56              
57             # Add more keys to the self hash.
58 1         2 my @self_keys = ( legal_keys( %{$self} ), );
  1         3  
59 1         10 unlock_keys( %{$self} );
  1         3  
60 1         8 lock_keys_plus( %{$self}, @self_keys );
  1         3  
61 1         32 my $config = IO::Iron::Common::get_config(%params);
62 1         5 $log->debugf( 'The config: %s', $config );
63 1 50       6 $self->{'project_id'} = defined $config->{'project_id'} ? $config->{'project_id'} : undef;
64 1         3 assert_nonblank( $self->{'project_id'}, 'self->{project_id} is not defined or is blank' );
65              
66 1         10 unlock_keys( %{$self} );
  1         10  
67 1         10 bless $self, $class;
68 1         2 lock_keys( %{$self}, @self_keys );
  1         8  
69              
70             # Set up the connection client
71             my $connection = IO::Iron::Connection->new(
72             {
73             'project_id' => $config->{'project_id'},
74             'token' => $config->{'token'},
75             'host' => defined $config->{'host'} ? $config->{'host'} : $DEFAULT_HOST,
76             'protocol' => $config->{'protocol'},
77             'port' => $config->{'port'},
78             'api_version' => defined $config->{'api_version'} ? $config->{'api_version'} : $DEFAULT_API_VERSION,
79             'timeout' => $config->{'timeout'},
80 1 50       54 'connector' => $params{'connector'},
    50          
81             }
82             );
83 1         6 $self->{'connection'} = $connection;
84             $log->debugf(
85             'IronWorker Client created with config: (project_id=%s; token=%s; host=%s; timeout=%s).',
86             $config->{'project_id'},
87 1         7 $config->{'token'}, $config->{'host'}, $config->{'timeout'}
88             );
89 1         4 $log->tracef( 'Exiting new: %s', $self );
90 1         10 return $self;
91             }
92              
93             ###############################################
94             ######## FUNCTIONS: CODE PACKAGES #############
95             ###############################################
96              
97             sub list_code_packages {
98 0     0 1   my ($self) = @_;
99 0           $log->tracef('Entering list_code_packages()');
100              
101 0           my $connection = $self->{'connection'};
102 0           my ( $http_status_code, $response_message ) =
103             $connection->perform_iron_action( IO::Iron::IronWorker::Api::IRONWORKER_LIST_CODE_PACKAGES(), {} );
104 0           $self->{'last_http_status_code'} = $http_status_code;
105 0           my @codes;
106 0           foreach ( @{$response_message} ) {
  0            
107 0           push @codes, $_;
108             }
109 0           $log->debugf( 'Returning %d code packages.', scalar @codes );
110 0           $log->tracef( 'Exiting list_code_packages: %s', \@codes );
111 0           return @codes;
112             }
113              
114             sub update_code_package {
115 0     0 1   my $self = shift;
116 0           my %params = validate(
117             @_,
118             {
119             'name' => { type => SCALAR, }, # Code package name.
120             'file' => {
121             type => SCALAR,
122             optional => 1, # The zip archive as a string buffer.
123             depends => [ 'file_name', 'runtime' ],
124             },
125             'file_name' => { type => SCALAR, optional => 1 }, # Name of the zip file, not
126             'runtime' => { type => SCALAR, optional => 1, }, # The runtime type, e.g. sh, perl, ruby.
127             'config' => { type => SCALAR, optional => 1, },
128             'max_concurrency' => { type => SCALAR, optional => 1, },
129             'retries' => { type => SCALAR, optional => 1, },
130             'retries_delay' => { type => SCALAR, optional => 1, },
131             }
132             );
133 0           $log->tracef( 'Entering update_code_package(%s)', \%params );
134 0           my $connection = $self->{'connection'};
135 0           my ( $http_status_code, $response_message ) = $connection->perform_iron_action(
136             IO::Iron::IronWorker::Api::IRONWORKER_UPLOAD_OR_UPDATE_A_CODE_PACKAGE(),
137             {
138             'body' => \%params,
139             }
140             );
141 0           $self->{'last_http_status_code'} = $http_status_code;
142 0           my $id = $response_message->{'id'};
143 0           $log->tracef( 'Exiting update_code_package: %s', $id );
144 0           return $id;
145             }
146              
147             sub get_info_about_code_package {
148 0     0 1   my $self = shift;
149 0           my %params = validate(
150             @_,
151             {
152             'id' => { type => SCALAR, }, # Code package id.
153             }
154             );
155 0           $log->tracef( 'Entering get_info_about_code_package(%s)', \%params );
156 0           my $connection = $self->{'connection'};
157             my ( $http_status_code, $response_message ) =
158             $connection->perform_iron_action( IO::Iron::IronWorker::Api::IRONWORKER_GET_INFO_ABOUT_A_CODE_PACKAGE(),
159 0           { '{Code ID}' => $params{'id'}, } );
160 0           $self->{'last_http_status_code'} = $http_status_code;
161 0           my $info = $response_message;
162 0           $log->tracef( 'Exiting get_info_about_code_package: %s', $info );
163 0           return $info;
164             }
165              
166             sub delete_code_package {
167 0     0 1   my $self = shift;
168 0           my %params = validate(
169             @_,
170             {
171             'id' => { type => SCALAR, }, # Code package id.
172             }
173             );
174 0           $log->tracef( 'Entering delete_code_package(%s)', $params{'id'} );
175 0           my $connection = $self->{'connection'};
176             my ( $http_status_code, $response_message ) = $connection->perform_iron_action(
177             IO::Iron::IronWorker::Api::IRONWORKER_DELETE_A_CODE_PACKAGE(),
178             {
179 0           '{Code ID}' => $params{'id'},
180             }
181             );
182 0           $self->{'last_http_status_code'} = $http_status_code;
183 0           $log->tracef( 'Exiting delete_code_package: %d', 1 );
184 0           return 1;
185             }
186              
187             sub download_code_package {
188 0     0 1   my $self = shift;
189 0           my %params = validate(
190             @_,
191             {
192             'id' => { type => SCALAR, }, # Code package id.
193             'revision' => { type => SCALAR, optional => 1, }, # Code package revision.
194             }
195             );
196 0           $log->tracef( 'Entering download_code_package(%s)', \%params );
197 0           my $connection = $self->{'connection'};
198 0           my %query_params;
199 0 0         $query_params{'{revision}'} = $params{'revision'} if $params{'revision'}; ## no critic (ControlStructures::ProhibitPostfixControls)
200             my ( $http_status_code, $response_message ) = $connection->perform_iron_action(
201             IO::Iron::IronWorker::Api::IRONWORKER_DOWNLOAD_A_CODE_PACKAGE(),
202             {
203 0           '{Code ID}' => $params{'id'},
204             %query_params,
205             }
206             );
207 0           my $code_package = $response_message->{'file'};
208 0           my $file_name = $response_message->{'file_name'};
209 0           $self->{'last_http_status_code'} = $http_status_code;
210              
211 0           $log->tracef( 'Exiting download_code_package:%s, %s', $code_package, $file_name );
212 0           return ( $code_package, $file_name );
213             }
214              
215             sub list_code_package_revisions {
216 0     0 1   my $self = shift;
217 0           my %params = validate(
218             @_,
219             {
220             'id' => { type => SCALAR, }, # Code package id.
221             }
222             );
223 0           $log->tracef( 'Entering list_code_package_revisions(%s)', $params{'id'} );
224              
225 0           my $connection = $self->{'connection'};
226             my ( $http_status_code, $response_message ) =
227             $connection->perform_iron_action( IO::Iron::IronWorker::Api::IRONWORKER_LIST_CODE_PACKAGE_REVISIONS(),
228 0           { '{Code ID}' => $params{'id'}, } );
229 0           my @revisions;
230 0           foreach ( @{$response_message} ) {
  0            
231 0           push @revisions, $_;
232             }
233 0           $log->debugf( 'Returning %d code packages.', scalar @revisions );
234 0           $log->tracef( 'Exiting list_code_package_revisions: %s', \@revisions );
235 0           return @revisions;
236             }
237              
238             ###############################################
239             ######## FUNCTIONS: TASK ######################
240             ###############################################
241              
242             sub create_task {
243 0     0 1   my $self = shift;
244             my %params = validate_with(
245             'params' => \@_,
246 0     0     'normalize_keys' => sub { return lc shift },
247 0           'spec' => {
248             'code_name' => { type => SCALAR, }, # Code package name.
249             'payload' => { type => SCALAR, }, # Payload
250             },
251             'allow_extra' => 1,
252             );
253 0           $log->tracef( 'Entering create_task(%s)', \%params );
254              
255 0           my $connection = $self->{'connection'};
256              
257 0           my $task = IO::Iron::IronWorker::Task->new(
258             {
259             'ironworker_client' => $self, # Pass a reference to the parent object.
260             'connection' => $connection,
261             %params,
262             }
263             );
264              
265 0           $log->tracef( 'Exiting create_task: %s', $task );
266 0           return $task;
267             }
268              
269             sub tasks {
270 0     0 1   my $self = shift;
271 0           my %params = validate(
272             @_,
273             {
274             'code_name' => { type => SCALAR, }, # Code package name.
275             'queued' => { type => SCALAR, 'optional' => 1, },
276             'running' => { type => SCALAR, 'optional' => 1, },
277             'complete' => { type => SCALAR, 'optional' => 1, },
278             'error' => { type => SCALAR, 'optional' => 1, },
279             'cancelled' => { type => SCALAR, 'optional' => 1, },
280             'killed' => { type => SCALAR, 'optional' => 1, },
281             'from_time' => { type => SCALAR, 'optional' => 1, },
282             'to_time' => { type => SCALAR, 'optional' => 1, },
283             }
284             );
285 0           $log->tracef( 'Entering tasks(%s, %s)', \%params );
286              
287 0           my $code_name = $params{'code_name'};
288 0           delete $params{'code_name'};
289 0           my $connection = $self->{'connection'};
290 0           my ( $http_status_code, $response_message ) = $connection->perform_iron_action(
291             IO::Iron::IronWorker::Api::IRONWORKER_LIST_TASKS(),
292             {
293             '{code_name}' => $code_name,
294             %params,
295             }
296             );
297 0           $self->{'last_http_status_code'} = $http_status_code;
298 0           my @tasks;
299 0           foreach ( @{$response_message} ) {
  0            
300 0           $log->debugf( 'task info:%s', $_ );
301             push @tasks,
302             $self->create_task(
303             'code_name' => $_->{'code_name'},
304             'payload' => $_->{'payload'} ? $_->{'payload'} : q{},
305 0 0         %{$_},
  0            
306             );
307             }
308 0           $log->debugf( 'Returning %d tasks.', scalar @tasks );
309 0           $log->tracef( 'Exiting tasks: %s', \@tasks );
310 0           return @tasks;
311             }
312              
313             sub queue {
314 0     0 1   my $self = shift;
315 0           my %validate_params = (
316             'tasks' => { type => OBJECT | ARRAYREF, }, # ref to task.
317             );
318 0           my %params = validate( @_, {%validate_params} );
319 0           lock_keys( %params, keys %validate_params );
320 0           $log->tracef( 'Entering queue(%s)', \%params );
321              
322 0           my $connection = $self->{'connection'};
323 0           my @message_tasks;
324 0 0         if ( ref $params{'tasks'} eq 'IO::Iron::IronWorker::Task' ) {
325 0           $params{'tasks'} = [ $params{'tasks'} ];
326             }
327 0           foreach my $task ( @{ $params{'tasks'} } ) {
  0            
328 0           assert_isa( $task, 'IO::Iron::IronWorker::Task', 'task is IO::Iron::IronWorker::Task.' );
329 0           my %task_body;
330 0           $task_body{'code_name'} = $task->{'code_name'};
331 0           $task_body{'payload'} = $task->{'payload'};
332 0 0         $task_body{'priority'} = $task->{'priority'} if defined $task->{'priority'};
333 0 0         $task_body{'timeout'} = $task->{'timeout'} if defined $task->{'timeout'};
334 0 0         $task_body{'delay'} = $task->{'delay'} if defined $task->{'delay'};
335 0 0         $task_body{'name'} = $task->{'name'} if defined $task->{'name'};
336 0           push @message_tasks, \%task_body;
337             }
338              
339 0           my %message_body = ( 'tasks' => \@message_tasks );
340 0           my ( $http_status_code, $response_message ) = $connection->perform_iron_action(
341             IO::Iron::IronWorker::Api::IRONWORKER_QUEUE_A_TASK(),
342             {
343             'body' => \%message_body,
344             }
345             );
346 0           $self->{'last_http_status_code'} = $http_status_code;
347              
348 0           my @ids;
349 0           my @ret_tasks = ( @{ $response_message->{'tasks'} } ); # tasks.
  0            
350 0           foreach my $task ( @{ $params{'tasks'} } ) {
  0            
351 0           my $task_info = shift @ret_tasks; # We are using the same task objects.
352 0           push @ids, $task_info->{'id'};
353 0           $task->id( $task_info->{'id'} );
354             }
355              
356             #assert_is($response_message->{'msg'}, 'Queued up'); # Could be dangerous!
357 0           $log->debugf( 'Queued IronWorker Task(s) (task id(s)=%s).', ( join q{,}, @ids ) );
358 0 0         if (wantarray) {
359 0           $log->tracef( 'Exiting queue: %s', ( join q{:}, @ids ) );
360 0           return @ids;
361             }
362             else {
363 0 0         if ( scalar @{ $params{'tasks'} } == 1 ) {
  0            
364 0           $log->tracef( 'Exiting queue: %s', $ids[0] );
365 0           return $ids[0];
366             }
367             else {
368 0           $log->tracef( 'Exiting queue: %s', scalar @ids );
369 0           return scalar @ids;
370             }
371             }
372             }
373              
374             sub get_info_about_task {
375 0     0 1   my $self = shift;
376 0           my %params = validate(
377             @_,
378             {
379             'id' => { type => SCALAR, }, # task id.
380             }
381             );
382 0           $log->tracef( 'Entering get_info_about_task(%s)', \%params );
383              
384 0           my $connection = $self->{'connection'};
385             my ( $http_status_code, $response_message ) =
386             $connection->perform_iron_action( IO::Iron::IronWorker::Api::IRONWORKER_GET_INFO_ABOUT_A_TASK(),
387 0           { '{Task ID}' => $params{'id'}, } );
388 0           $self->{'last_http_status_code'} = $http_status_code;
389 0           my $info = $response_message;
390 0           $log->tracef( 'Exiting get_info_about_task: %s', $info );
391 0           return $info;
392             }
393              
394             ###############################################
395             ######## FUNCTIONS: SCHEDULED TASK ############
396             ###############################################
397              
398             sub scheduled_tasks {
399 0     0 1   my ($self) = @_;
400 0           $log->tracef('Entering scheduled_tasks()');
401              
402 0           my $connection = $self->{'connection'};
403 0           my ( $http_status_code, $response_message ) =
404             $connection->perform_iron_action( IO::Iron::IronWorker::Api::IRONWORKER_LIST_SCHEDULED_TASKS(), {} );
405 0           $self->{'last_http_status_code'} = $http_status_code;
406 0           my @tasks;
407 0           foreach ( @{$response_message} ) {
  0            
408 0           $log->debugf( 'task info:%s', $_ );
409             push @tasks,
410             $self->create_task(
411             'code_name' => $_->{'code_name'},
412             'payload' => $_->{'payload'} ? $_->{'payload'} : q{},
413 0 0         %{$_},
  0            
414             );
415             }
416 0           $log->debugf( 'Returning %d tasks.', scalar @tasks );
417 0           $log->tracef( 'Exiting scheduled_tasks: %s', \@tasks );
418 0           return @tasks;
419             }
420              
421             sub schedule {
422 0     0 1   my $self = shift;
423 0           my %validate_params = (
424             'tasks' => { type => OBJECT | ARRAYREF, }, # ref to task.
425             );
426 0           my %params = validate( @_, {%validate_params} );
427 0           lock_keys( %params, keys %validate_params );
428 0           $log->tracef( 'Entering schedule(%s)', \%params );
429              
430 0           my $connection = $self->{'connection'};
431 0           my @message_tasks;
432 0 0         if ( ref $params{'tasks'} eq 'IO::Iron::IronWorker::Task' ) {
433 0           $log->debugf('The parameter is a single object.');
434 0           $params{'tasks'} = [ $params{'tasks'} ];
435             }
436 0           foreach my $task ( @{ $params{'tasks'} } ) {
  0            
437 0           assert_isa( $task, 'IO::Iron::IronWorker::Task', 'task is IO::Iron::IronWorker::Task.' );
438 0           my %task_body;
439 0           $task_body{'code_name'} = $task->{'code_name'};
440 0           $task_body{'payload'} = $task->{'payload'};
441 0 0         $task_body{'run_every'} = $task->{'run_every'} if defined $task->{'run_every'};
442 0 0         $task_body{'end_at'} = $task->{'end_at'} if defined $task->{'end_at'};
443 0 0         $task_body{'run_times'} = $task->{'run_times'} if defined $task->{'run_times'};
444 0 0         $task_body{'priority'} = $task->{'priority'} if defined $task->{'priority'};
445 0 0         $task_body{'start_at'} = $task->{'start_at'} if defined $task->{'start_at'};
446 0 0         $task_body{'name'} = $task->{'name'} if defined $task->{'name'}; # Hm... documents do not mention but example does...
447 0           push @message_tasks, \%task_body;
448             }
449              
450 0           my %message_body = ( 'schedules' => \@message_tasks );
451 0           my ( $http_status_code, $response_message ) = $connection->perform_iron_action(
452             IO::Iron::IronWorker::Api::IRONWORKER_SCHEDULE_A_TASK(),
453             {
454             'body' => \%message_body,
455             }
456             );
457 0           $self->{'last_http_status_code'} = $http_status_code;
458              
459 0           my ( @ids, $msg ); ## no critic (Variables::ProhibitUnusedVariables)
460 0           my @ret_tasks = ( @{ $response_message->{'schedules'} } ); # scheduled tasks.
  0            
461 0           foreach my $task ( @{ $params{'tasks'} } ) {
  0            
462 0           my $task_info = shift @ret_tasks;
463 0           push @ids, $task_info->{'id'};
464 0           $task->id( $task_info->{'id'} );
465             }
466              
467             #assert_is($response_message->{'msg'}, 'Scheduled'); # Could be dangerous!
468             ## It was dangerous! Failing discovered 2014-08-10, response message has dropped the field "msg"!
469 0           $log->debugf( 'Scheduled IronWorker Task(s) (task id(s)=%s).', ( join q{,}, @ids ) );
470 0 0         if (wantarray) {
471 0           $log->tracef( 'Exiting schedule: %s', ( join q{:}, @ids ) );
472 0           return @ids;
473             }
474             else {
475 0 0         if ( scalar @{ $params{'tasks'} } == 1 ) {
  0            
476 0           $log->tracef( 'Exiting schedule: %s', $ids[0] );
477 0           return $ids[0];
478             }
479             else {
480 0           $log->tracef( 'Exiting schedule: %s', scalar @ids );
481 0           return scalar @ids;
482             }
483             }
484             }
485              
486             sub get_info_about_scheduled_task {
487 0     0 1   my $self = shift;
488 0           my %params = validate(
489             @_,
490             {
491             'id' => { type => SCALAR, }, # task id.
492             }
493             );
494 0           $log->tracef( 'Entering get_info_about_scheduled_task(%s)', \%params );
495              
496 0           my $connection = $self->{'connection'};
497             my ( $http_status_code, $response_message ) =
498             $connection->perform_iron_action( IO::Iron::IronWorker::Api::IRONWORKER_GET_INFO_ABOUT_A_SCHEDULED_TASK(),
499 0           { '{Schedule ID}' => $params{'id'}, } );
500 0           $self->{'last_http_status_code'} = $http_status_code;
501 0           my $info = $response_message;
502 0           $log->tracef( 'Exiting get_info_about_scheduled_task: %s', $info );
503 0           return $info;
504             }
505              
506             ###############################################
507             ########### FUNCTIONS: STACKS #################
508             ###############################################
509              
510             sub list_available_stacks {
511 0     0 1   my ($self) = @_;
512 0           $log->tracef('Entering list_available_stacks()');
513              
514 0           my $connection = $self->{'connection'};
515 0           my ( $http_status_code, $response_message ) =
516             $connection->perform_iron_action( IO::Iron::IronWorker::Api::IRONWORKER_LIST_OF_AVAILABLE_STACKS(), {} );
517 0           $self->{'last_http_status_code'} = $http_status_code;
518 0           my @stacks;
519 0           foreach ( @{$response_message} ) {
  0            
520 0           push @stacks, $_;
521             }
522 0           $log->debugf( 'Returning %d stacks.', scalar @stacks );
523 0           $log->tracef( 'Exiting list_available_stacks(): %s', \@stacks );
524 0           return @stacks;
525             }
526              
527             1;
528              
529             __END__
530              
531             =pod
532              
533             =encoding UTF-8
534              
535             =head1 NAME
536              
537             IO::Iron::IronWorker::Client - IronWorker (Online Worker Platform) Client.
538              
539             =head1 VERSION
540              
541             version 0.14
542              
543             =head1 SYNOPSIS
544              
545             require IO::Iron::IronWorker::Client;
546              
547             my $ironworker_client = IO::Iron::IronWorker::Client->new();
548             # or
549             use IO::Iron qw(ironworker);
550             my $iron_worker_client = ironworker();
551              
552             my $unique_code_package_name = 'HelloWorldCode';
553             my $worker_as_zip; # Zipped Perl script and dependencies.
554             my $unique_code_executable_file_name = 'HelloWorldCode.pl';
555             my $uploaded = $iron_worker_client->update_code_package(
556             'name' => $unique_code_package_name,
557             'file' => $worker_as_zip,
558             'file_name' => $unique_code_executable_file_name,
559             'runtime' => 'perl',
560             );
561              
562             my $code_package_id;
563             my @code_packages = $iron_worker_client->list_code_packages();
564             foreach (@code_packages) {
565             if($_->{'name'} eq $unique_code_package_name) {
566             $code_package_id = $_->{'id'};
567             last;
568             }
569             }
570              
571             my $code_package = $iron_worker_client->get_info_about_code_package(
572             'id' => $code_package_id
573             );
574              
575             my @code_package_revisions = $iron_worker_client->
576             list_code_package_revisions( 'id' => $code_package_id );
577              
578             my ($downloaded, $file_name) = $iron_worker_client->download_code_package(
579             'id' => $code_package_id,
580             'revision' => 1,
581             );
582              
583             my $delete_rval = $iron_worker_client->delete( 'id' => $code_package_id );
584              
585             # Tasks
586             my $task_payload = 'Task payload (can be JSONized)';
587             my $task = $iron_worker_client->create_task(
588             'code_name' => $unique_code_package_name,
589             'payload' => $task_payload,
590             #
591             # additional parameters for a task:
592             # 'priority', # The priority queue to run the task in.
593             # Valid values are 0, 1, and 2. 0 is the default.
594             # 'timeout', # The maximum runtime of your task in seconds.
595             # 'delay', # The number of seconds to delay before actually
596             # queuing the task. Default is 0.
597             # For scheduled task:
598             # 'priority', # The priority queue to run the task in.
599             # Valid values are 0, 1, and 2. 0 is the default.
600             # 'run_every', # The amount of time, in seconds, between runs
601             # 'end_at', # The time tasks will stop being queued.
602             # Should be a time or datetime.
603             # 'run_times', # The number of times a task will run.
604             # 'start_at', # The time the scheduled task should first be run.
605             # 'name', # Name of task or scheduled task.
606             );
607              
608             # When queuing, the task object is updated with returned id.
609             my $task_id = $iron_worker_client->queue( 'tasks' => $task );
610             # Or:
611             my $task1 = $iron_worker_client->create_task(
612             'code_name' => $unique_code_package_name,
613             'payload' => $task_payload,
614             );
615             my $task2 = $iron_worker_client->create_task(
616             'code_name' => $unique_code_package_name,
617             'payload' => $task_payload,
618             );
619             my @task_ids = $iron_worker_client->queue( 'tasks' => [ $task1, $task2 ] );
620             # Or:
621             my $number_of_tasks_queued = $iron_worker_client->queue(
622             'tasks' => [ $task1, $task2 ]
623             );
624             #
625             $task_id = $task->id();
626             my $task_info = $iron_worker_client->get_info_about_task( 'id' => $task_id );
627             until ($task_info->{'status'} =~ /(complete|error|killed|timeout)/) {
628             sleep 3;
629             $task_info = $iron_worker_client->get_info_about_task( 'id' => $task_id );
630             }
631             # $task->status() updates the task's information.
632             my $task_duration = $task->duration();
633             my $task_end_time = $task->end_time();
634             my $task_updated_at = $task->updated_at();
635             my $task_log = $task->log(); # Log is text/plain
636             my $cancelled = $task->cancel();
637             my $progress_set = $task->progress( {
638             'percent' => 25,
639             'msg' => 'Not even halfway through!',
640             } );
641             my $retried = $task->retry();
642             $task_id = $task->id(); # New task id after retry().
643              
644             $task_info = $iron_worker_client->get_info_about_task( 'id' => $task_id );
645              
646             # Schedule task.
647             my $schedule_task = $iron_worker_client->create_task(
648             $unique_code_package_name,
649             $task_payload,
650             'priority' => 0,
651             'run_every' => 120, # Every two minutes.
652             );
653             $schedule_task->run_times(5);
654             use DateTime;
655             use DateTime::Format::ISO8601;
656             my $dt = DateTime->now;
657             $dt->add( hours => 1 );
658             my $start_dt = DateTime::Format::ISO8601->format_datetime( $dt );
659             $dt->add( hours => 3 );
660             my $end_dt = DateTime::Format::ISO8601->format_datetime( $dt );
661             $schedule_task->start_at( $start_dt );
662             $schedule_task->end_at( $end_dt );
663             #
664             # When scheduling, the task object is updated with returned id.
665             $schedule_task = $iron_worker_client->schedule( 'tasks' => $schedule_task);
666             # Or:
667             my $schedule_task1 = $iron_worker_client->create_task(
668             'code_name' => $unique_code_package_name,
669             'payload' => $task_payload,
670             'start_at' => $start_dt,
671             );
672             my $schedule_task2 = $iron_worker_client->create_task(
673             'code_name' => $unique_code_package_name,
674             'payload' => $task_payload,
675             'start_at' => $start_dt,
676             );
677             my @scheduled_tasks = $iron_worker_client->schedule(
678             'tasks' => [$schedule_task1, $schedule_task2]
679             );
680             # Or:
681             my $number_of_scheduled_tasks = $iron_worker_client->schedule(
682             'tasks' => [$schedule_task1, $schedule_task2]
683             );
684             #
685              
686             my $scheduled_task_info = $iron_worker_client->
687             get_info_about_scheduled_task( 'id' => $task_id );
688              
689              
690             my $from_time = time - (24*60*60);
691             my $to_time = time - (1*60*60);
692             my @tasks = $iron_worker_client->tasks(
693             'code_name' => $unique_code_package_name, # Mandatory
694             'status' => qw{queued running complete error cancelled killed timeout},
695             'from_time' => $from_time, # Number of seconds since the Unix epoc
696             'to_time' => $to_time, # Number of seconds since the Unix epoc
697             );
698              
699             @scheduled_tasks = $iron_worker_client->scheduled_tasks();
700              
701             =head1 DESCRIPTION
702              
703             IO::Iron::IronWorker is a client for the IronWorker remote worker system at L<http://www.iron.io/|http://www.iron.io/>.
704             IronWorker is a cloud based parallel multi-language worker platform. with a REST API.
705             IO::Iron::IronWorker creates a Perl object for interacting with IronWorker.
706             All IronWorker functions are available.
707              
708             The class IO::Iron::IronWorker::Client instantiates the 'project', IronWorker access configuration.
709              
710             =head2 IronWorker Cloud Parallel Workers
711              
712             L<http://www.iron.io/|http://www.iron.io/>
713              
714             IronWorker is a parallel worker platform delivered as a service to Internet connecting
715             applications via its REST interface. Built with distributed
716             cloud applications in mind, it provides on-demand scalability for workers,
717             controls with HTTPS transport and cloud-optimized performance. [see L<http://www.iron.io/|http://www.iron.io/>]
718              
719             =head2 Using the IronWorker Client Library
720              
721             IO::Iron::IronWorker::Client is a normal Perl package meant to be used as an object.
722              
723             require IO::Iron::IronWorker::Client;
724             my $ironworker_client = IO::Iron::IronWorker::Client->new();
725              
726             Please see L<IO::Iron|IO::Iron> for further parameters and general usage.
727              
728             =head2 Commands
729              
730             After creating the client three sets of commands is available:
731              
732             =over 8
733              
734             =item Commands for operating code packages:
735              
736             =over 8
737              
738             =item IO::Iron::IronWorker::Client::list_code_packages()
739              
740             =item IO::Iron::IronWorker::Client::update_code_package(params)
741              
742             =item IO::Iron::IronWorker::Client::get_info_about_code_package('id' => code_package_id)
743              
744             =item IO::Iron::IronWorker::Client::delete_code_package('id' => code_package_id)
745              
746             =item IO::Iron::IronWorker::Client::download_code_package('id' => code_package_id, params)
747              
748             =item IO::Iron::IronWorker::Client::list_code_package_revisions('id' => code_package_id)
749              
750             =back
751              
752             =item Commands for operating tasks:
753              
754             =over 8
755              
756             =item IO::Iron::IronWorker::Client::create_task('code_name' => $name, 'payload' => $payload, ...)
757              
758             =item IO::Iron::IronWorker::Client::tasks('code_name' => $code_name, ...)
759              
760             =item IO::Iron::IronWorker::Client::queue(tasks => $task | [@tasks] )
761              
762             =item IO::Iron::IronWorker::Client::get_info_about_task('id => $id)
763              
764             =item IO::Iron::IronWorker::Task::log()
765              
766             =item IO::Iron::IronWorker::Task::cancel()
767              
768             =item IO::Iron::IronWorker::Task::set_progress('percent' => $number, 'msg' => $text)
769              
770             =item retry_task()
771              
772             =back
773              
774             =item Commands for operating scheduled tasks:
775              
776             =over 8
777              
778             =item IO::Iron::IronWorker::Client::scheduled_tasks('code_name' => $code_name, ...)
779              
780             =item IO::Iron::IronWorker::Client::schedule(tasks => $task | [@tasks] )
781              
782             =item IO::Iron::IronWorker::Client::get_info_about_scheduled_task('id' => $id)
783              
784             =item IO::Iron::IronWorker::Task::cancel_scheduled()
785              
786             =back
787              
788             =item Commands for stacks:
789              
790             =over 8
791              
792             =item IO::Iron::IronWorker::Client::list_scheduled_tasks()
793              
794             =back
795              
796             =back
797              
798             =head3 Operating code packages
799              
800             A code package is simply a script program packed into Zip archive together
801             with its dependency files (other libraries, configuration files, etc.).
802              
803             After creating the zip file and reading it into a perl variable, upload it.
804             In the following example, the worker contains only one file
805             and we create the archive in the program - as opposed to creating it before
806             and simply reading it from a file before uploading it.
807              
808             require IO::Iron::IronWorker::Client;
809             use IO::Compress::Zip;
810              
811             $iron_worker_client = IO::Iron::IronWorker::Client->new(
812             'config' => 'iron_worker.json'
813             );
814              
815             my $worker_as_string_ = <<EOF;
816             print qq{Hello, World!\n};
817             EOF
818             my $worker_as_zip;
819             my $worker_
820              
821             IO::Compress::Zip::zip(\$worker_as_string => \$worker_as_zip);
822              
823             my $code_package_return_id = $iron_worker_client->update_code_package(
824             'name' => 'HelloWorld_code_package',
825             'file' => $worker_as_string,
826             'file_name' => 'helloworld.pl',
827             'runtime' => 'perl',
828             );
829              
830             With method list_code_packages() you can retrieve information about all
831             the uploaded code packages. The method get_info_about_code_package()
832             will return information about only the requested code package.
833              
834             my @code_packages = $iron_worker_client->list_code_packages();
835             foreach (@code_packages) {
836             if($_->{'name'} eq 'HelloWorld_code_package) {
837             $code_package_id = $_->{'id'};
838             last;
839             }
840             }
841             my $code_package = $iron_worker_client->get_info_about_code_package(
842             'id' => $code_package_id,
843             );
844              
845             Method delete_code_package() removes the code package from IronWorker service.
846              
847             my $deleted = $iron_worker_client->delete_code_package(
848             'id' => $code_package_id,
849             );
850              
851             The uploaded code package can be retrieved with method download_code_package().
852             The downloaded file is a zip archive.
853              
854             my ($downloaded, $file_name) = $iron_worker_client->download_code_package(
855             'id' => $code_package_id, 'revision' => 1,
856             );
857              
858             The code packages get revision numbers according to their upload order.
859             The first upload of a code package gets revision number 1. Any subsequent
860             upload of the same code package (same name) will get one higher
861             revision number so the different uploads can be recognized.
862              
863             my @code_package_revisions = $iron_worker_client->list_code_package_revisions(
864             'id' => $code_package_id,
865             );
866              
867             =head3 Operating tasks
868              
869             Every task needs two parameters: the name of the code package on whose
870             code they will run and a payload. The payload is passed to the code
871             package as a file. Payload is mandatory so if your code doesn't need it,
872             just insert an empty string. Payload can be any string, or stringified
873             object, normally JSON.
874              
875             my $task_payload = 'Task payload (could be JSONized object)';
876             my $task = $iron_worker_client->create_task(
877             'code_name' => $unique_code_package_name,
878             'payload' => $task_payload,
879             'priority' => 0,
880             );
881             my $task_code_package_name = $task->code_package_name();
882              
883             Queue the task, i.e. put it to the queue for immediate execution.
884             "Immediate" doesn't mean that IronWorker will execute it right away,
885             just ASAP according to priority and delay parameters. When queuing,
886             the task object is updated with returned id.
887              
888             my $task_id = $iron_worker_client->queue('task' => $task);
889             # Or:
890             my @task_ids = $iron_worker_client->queue('task' => [$task1, $task2, ]);
891             # Or:
892             my $number_of_tasks_queued = $iron_worker_client->queue(
893             'task' => [$task1, $task2],
894             );
895              
896             Read the STDOUT log of the task.
897              
898             my $task_log = $task->log(); # Log is mime type text/plain
899              
900             Cancel task if it's still in queue or currently being executed.
901              
902             my $cancelled = $task->cancel();
903              
904             Change the "progress display" of the task.
905              
906             my $progress_set = $task->progress(
907             'percent' => 25,
908             'msg' => 'Not even halfway through!',
909             );
910              
911             Retry a failed task. You cannot change the payload. If the
912             payload is faulty, then you need to create a new task.
913              
914             my $new_task_id = $task->retry();
915             # New task id after retry().
916              
917             Get info about a task. Info is a hash structure.
918              
919             my $task_info = $iron_worker_client->get_info_about_task( 'task' => $task_id );
920              
921             =head3 Operating scheduled tasks
922              
923             Create a new task for scheduling.
924              
925             my $schedule_task = $iron_worker_client->create_task(
926             'code_name' => $unique_code_package_name,
927             'payload' => $task_payload,
928             'priority' => 0,
929             'run_every' => 120, # Every two minutes.
930             );
931              
932             Schedule the task or tasks. When scheduling, the task object is updated with returned id.
933              
934             $schedule_task = $iron_worker_client->schedule('task' => $schedule_task);
935             # Or:
936             my @scheduled_tasks = $iron_worker_client->schedule(
937             'task' => [$schedule_task1, $schedule_task2]
938             );
939             # Or:
940             my $number_of_scheduled_tasks = $iron_worker_client->schedule(
941             'task' => [$schedule_task1, $schedule_task2]
942             );
943              
944             Get information about the scheduled task.
945              
946             my $scheduled_task_info = $iron_worker_client->get_info_about_scheduled_task(
947             'id' => $task_id
948             );
949              
950             Get all scheduled tasks as IO::Iron::IronWorker::Task objects.
951              
952             my @scheduled_tasks = $iron_worker_client->scheduled_tasks();
953              
954             =head3 Stacks
955              
956             Get a list of all available stacks.
957              
958             my @stacks = $iron_worker_client->list_available_stacks();
959              
960             =head3 Exceptions
961              
962             A REST call to IronWorker server may fail for several reason.
963             All failures generate an exception using the L<Exception::Class|Exception::Class> package.
964             Class IronHTTPCallException contains the field status_code, response_message and error.
965             Error is formatted as such: IronHTTPCallException: status_code=<HTTP status code> response_message=<response_message>.
966              
967             use Try::Tiny;
968             use Scalar::Util qw{blessed};
969             try {
970             my $queried_iron_mq_queue_01 = $iron_mq_client->get_queue($unique_queue_name_01);
971             }
972             catch {
973             die $_ unless blessed $_ && $_->can('rethrow');
974             if ( $_->isa('IronHTTPCallException') ) {
975             if ($_->status_code == 404) {
976             print "Bad things! Can not just find the catch in this!\n";
977             }
978             }
979             else {
980             $_->rethrow; # Push the error upwards.
981             }
982             };
983              
984             =for stopwords IronWorker multi API scalability HTTPS optimized params msg perl
985              
986             =for stopwords recognized stringified JSON STDOUT IronHTTPCallException Params runtime config subparam
987              
988             =for stopwords Mikko Koivunalho perldoc CPAN AnnoCPAN ACKNOWLEDGMENTS tradename licensable MERCHANTABILITY
989              
990             =head1 REQUIREMENTS
991              
992             See L<IO::Iron|IO::Iron> for requirements.
993              
994             =head1 SUBROUTINES/METHODS
995              
996             =head2 new
997              
998             Creator function.
999              
1000             =head2 list_code_packages
1001              
1002             Return a list of hashes containing information about every code package in IronWorker.
1003              
1004             =over 8
1005              
1006             =item Params: [None]
1007              
1008             =item Return: List of hashes.
1009              
1010             =back
1011              
1012             See L</get_info_about_code_package> for an example of the returned hashes.
1013              
1014             =head2 update_code_package
1015              
1016             Upload an IronWorker code package or update an existing code package.
1017              
1018             =over 8
1019              
1020             =item Params:
1021              
1022             =over 8
1023              
1024             =item name, code package name, mandatory.
1025              
1026             =item file, the zip archive as a string buffer, optional if only updating other parameters.
1027              
1028             =item file_name, the zip archive name, required if parameter 'file' is present.
1029              
1030             =item runtime, the runtime type, e.g. sh, perl or ruby, required if parameter 'file' is present.
1031              
1032             =item config, an (configuration) file for the code package, optional.
1033              
1034             =item max_concurrency, number of concurrent runs, optional.
1035              
1036             =item retries, number of retries, optional.
1037              
1038             =item retries_delay, delay between retries, optional.
1039              
1040             =back
1041              
1042             =item Return: if successful, a new code package id.
1043              
1044             =item Exception: IronHTTPCallException if fails. (IronHTTPCallException: status_code=<HTTP status code> response_message=<response_message>)
1045              
1046             =back
1047              
1048             =head2 get_info_about_code_package
1049              
1050             =over 8
1051              
1052             =item Params: code package id.
1053              
1054             =item Return: a hash containing info about code package. Exception if code packages does not exist.
1055              
1056             =item Exception: IronHTTPCallException if fails. (IronHTTPCallException: status_code=<HTTP status code> response_message=<response_message>)
1057              
1058             =back
1059              
1060             Sample response (in JSON format):
1061              
1062             {
1063             "id": "4eb1b241cddb13606500000b",
1064             "project_id": "4eb1b240cddb13606500000a",
1065             "name": "MyWorker",
1066             "runtime": "ruby",
1067             "latest_checksum": "a0702e9e9a84b758850d19ddd997cf4a",
1068             "rev": 1,
1069             "latest_history_id": "4eb1b241cddb13606500000c",
1070             "latest_change": 1328737460598000000
1071             }
1072              
1073             =head2 delete_code_package
1074              
1075             Delete an IronWorker code package.
1076              
1077             =over 8
1078              
1079             =item Params: code package id. Code package must exist. If not, fails with an exception.
1080              
1081             =item Return: 1 == success.
1082              
1083             =item Exception: IronHTTPCallException if fails. (IronHTTPCallException: status_code=<HTTP status code> response_message=<response_message>)
1084              
1085             =back
1086              
1087             =head2 download_code_package
1088              
1089             Download an IronWorker code package.
1090              
1091             =over 8
1092              
1093             =item Params: code package id. Code package must exist. If not, fails with an exception.
1094             subparam: revision.
1095              
1096             =item Return: (list) the code package zipped (as it was uploaded), code package file name (with "_[1|later].zip" suffix).
1097              
1098             =item Exception: IronHTTPCallException if fails. (IronHTTPCallException: status_code=<HTTP status code> response_message=<response_message>)
1099              
1100             =back
1101              
1102             =head2 list_code_package_revisions
1103              
1104             Return a list of hashes containing information about one code package revisions.
1105              
1106             =over 8
1107              
1108             =item Params: code package id. Code package must exist. If not, fails with an exception.
1109              
1110             =item Return: List of hashes.
1111              
1112             =item Exception: IronHTTPCallException if fails. (IronHTTPCallException: status_code=<HTTP status code> response_message=<response_message>)
1113              
1114             =back
1115              
1116             =head2 create_task
1117              
1118             =over 8
1119              
1120             =item Params: code package name.
1121              
1122             =item Return: an object of class IO::Iron::IronWorker::Task.
1123              
1124             =back
1125              
1126             This method does not access the IronWorker service.
1127              
1128             =head2 tasks
1129              
1130             Return a list of objects of class IO::Iron::IronWorker::Task,
1131             every task in this IronWorker project.
1132              
1133             =over 8
1134              
1135             =item Params: code package name, params hash (status: queued|running|complete|error|cancelled|killed|timeout, from_time, to_time)
1136              
1137             =item Return: List of objects.
1138              
1139             =item Exception: IronHTTPCallException if fails. (IronHTTPCallException: status_code=<HTTP status code> response_message=<response_message>)
1140              
1141             =back
1142              
1143             =head2 queue
1144              
1145             Queue a new task or tasks for an IronWorker code package to execute.
1146              
1147             =over 8
1148              
1149             =item Params: one or more IO::Iron::IronWorker::Task objects.
1150              
1151             =item Return: task id(s) returned from IronWorker (if in list context),
1152             or number of tasks.
1153              
1154             =item Exception: IronHTTPCallException if fails. (IronHTTPCallException: status_code=<HTTP status code> response_message=<response_message>)
1155              
1156             =back
1157              
1158             =head2 get_info_about_task
1159              
1160             =over 8
1161              
1162             =item Params: task id.
1163              
1164             =item Return: a hash containing info about a task. Exception if the task does not exist.
1165              
1166             =item Exception: IronHTTPCallException if fails. (IronHTTPCallException: status_code=<HTTP status code> response_message=<response_message>)
1167              
1168             =back
1169              
1170             Sample response (in JSON format):
1171              
1172             {
1173             "id": "4eb1b471cddb136065000010",
1174             "project_id": "4eb1b46fcddb13606500000d",
1175             "code_id": "4eb1b46fcddb13606500000e",
1176             "code_history_id": "4eb1b46fcddb13606500000f",
1177             "status": "complete",
1178             "code_name": "MyWorker",
1179             "code_rev": "1",
1180             "start_time": 1320268924000000000,
1181             "end_time": 1320268924000000000,
1182             "duration": 43,
1183             "timeout": 3600,
1184             "payload": "{\"foo\":\"bar\"}",
1185             "updated_at": "2012-11-10T18:31:08.064Z",
1186             "created_at": "2012-11-10T18:30:43.089Z"
1187             }
1188              
1189             =head2 scheduled_tasks
1190              
1191             Return a list of objects of class IO::Iron::IronWorker::Task,
1192             every task in this IronWorker project.
1193              
1194             =over 8
1195              
1196             =item Params: code package name, params hash (status: queued|running|complete|error|cancelled|killed|timeout, from_time, to_time)
1197              
1198             =item Return: List of objects.
1199              
1200             =item Exception: IronHTTPCallException if fails. (IronHTTPCallException: status_code=<HTTP status code> response_message=<response_message>)
1201              
1202             =back
1203              
1204             =head2 schedule
1205              
1206             Schedule a new task or tasks for an IronWorker code package to execute.
1207              
1208             =over 8
1209              
1210             =item Params: one or more IO::Iron::IronWorker::Task objects.
1211              
1212             =item Return: scheduled task id(s) returned from IronWorker (if in list context),
1213             or number of scheduled tasks.
1214              
1215             =item Exception: IronHTTPCallException if fails. (IronHTTPCallException: status_code=<HTTP status code> response_message=<response_message>)
1216              
1217             =back
1218              
1219             =head2 get_info_about_scheduled_task
1220              
1221             =over 8
1222              
1223             =item Params: task id.
1224              
1225             =item Return: a hash containing info about a task. Exception if the task does not exist.
1226              
1227             =item Exception: IronHTTPCallException if fails. (IronHTTPCallException: status_code=<HTTP status code> response_message=<response_message>)
1228              
1229             =back
1230              
1231             Sample response (in JSON format):
1232              
1233             {
1234             "id": "4eb1b490cddb136065000011",
1235             "created_at": "2011-11-02T21:22:51Z",
1236             "updated_at": "2011-11-02T21:22:51Z",
1237             "project_id": "4eb1b46fcddb13606500000d",
1238             "msg": "Ran max times.",
1239             "status": "complete",
1240             "code_name": "MyWorker",
1241             "delay": 10,
1242             "start_at": "2011-11-02T21:22:34Z",
1243             "end_at": "2262-04-11T23:47:16Z",
1244             "next_start": "2011-11-02T21:22:34Z",
1245             "last_run_time": "2011-11-02T21:22:51Z",
1246             "run_times": 1,
1247             "run_count": 1
1248             }
1249              
1250             =head2 list_available_stacks
1251              
1252             Return a list of stacks available for running IronWorker code packages in.
1253              
1254             =over 8
1255              
1256             =item Params: [None]
1257              
1258             =item Return: List of strings.
1259              
1260             =back
1261              
1262             =head1 AUTHOR
1263              
1264             Mikko Koivunalho <mikko.koivunalho@iki.fi>
1265              
1266             =head1 BUGS
1267              
1268             Please report any bugs or feature requests to bug-io-iron@rt.cpan.org or through the web interface at:
1269             http://rt.cpan.org/Public/Dist/Display.html?Name=IO-Iron
1270              
1271             =head1 COPYRIGHT AND LICENSE
1272              
1273             This software is copyright (c) 2023 by Mikko Koivunalho.
1274              
1275             This is free software; you can redistribute it and/or modify it under
1276             the same terms as the Perl 5 programming language system itself.
1277              
1278             The full text of the license can be found in the
1279             F<LICENSE> file included with this distribution.
1280              
1281             =cut