File Coverage

blib/lib/App/Taskflow.pm
Criterion Covered Total %
statement 18 115 15.6
branch 0 50 0.0
condition 0 23 0.0
subroutine 6 11 54.5
pod 5 5 100.0
total 29 204 14.2


line stmt bran cond sub pod time code
1             package App::Taskflow;
2 1     1   19886 use v5.10;
  1         4  
  1         46  
3 1     1   896 use POSIX qw(setsid);
  1         6886  
  1         8  
4             our @EXPORT = qw/usage version taskflow daemonize/;# Symbols to autoexport (:DEFAULT tag)
5 1     1   1099 use base qw/Exporter/;
  1         7  
  1         110  
6 1     1   1370 use Log::Handler;
  1         67302  
  1         133  
7 1     1   4078 use DBM::Deep;
  1         16285  
  1         9  
8             $|++; # disable buffering on STDOUT - autoflush
9              
10             our $VERSION = '1.0';
11             our $re_line = qr/(?\w+):\s*(?

.+?)\s*(\[(?

\w+)\]\s*)?:\s*(?.*)\s*(?\&)?/;
12              
13             sub daemonize {
14 0 0   0 1   defined(my $pid = fork) or die "Can't fork: $!";
15 0 0         exit if $pid;
16 0 0         setsid or die "Can't start a new session: $!";
17 0           umask 0;
18             }
19              
20             sub load_config {
21 0     0 1   my $config_filename = shift;
22 0           my $data = shift;
23 0 0         return if (! -e $config_filename);
24 0           my $config_mt = (stat $config_filename )[9];
25              
26 0           my @config = ();
27 0           print '-'x10,' loading rules ','-'x10, "\n";
28 0           my $lines = do { # narrow scope
29 0           local $/; # Enter file slurp mode localized
30 0 0         open my $in_fh, '<', $config_filename or "Cannot read '$config_filename': $!\n";
31 0           <$in_fh>; # slurp whole input file in a run
32             };
33 0           for my $line ( split(/\n/, $lines) ) {
34 0 0 0       if ($line !~ /^#/ and $line =~ /:/) { # not starts with '#' and has ':'
35 0 0         if ($line =~ /$re_line/) {
36 0           print $line, "\n";
37 1     1   1636 my $name = $+{n};
  1         877  
  1         2917  
  0            
38 0           my $pattern = $+{p};
39 0   0       my $dt_str = $+{dt} // '1';
40 0           for (qw/1 s m h d/) {
41 0           $dt_str =~ s/s/*1/;
42 0           $dt_str =~ s/m/*60/;
43 0           $dt_str =~ s/h/*3600/;
44 0           $dt_str =~ s/d/*24*3600/;
45 0           $dt_str =~ s/w/*7*24*3600/;
46             }
47 0           my $dt = eval $dt_str;
48 0           my $command = $+{c};
49 0           my $ampersand = $+{a};
50 0           push @config, [$name,$pattern,$dt,$command,$ampersand];
51 0 0         $data->{$name} = () if ( !$data->{$name} );
52             }
53             }
54             }
55 0           print '-'x35, "\n";
56 0           return \@config, $config_mt;
57             }
58              
59             sub taskflow {
60 0     0 1   my ($folder, $logfile, $config_filename, $cache_filename, $target_name, $sleep) = @_;
61 0           my $log = Log::Handler->new(file => {
62             filename => $logfile,
63             maxlevel => "debug",
64             minlevel => "emerg",
65             message_layout => "%T [%L] %S: %m" });
66 0           my $data = DBM::Deep->new($cache_filename);
67 0           my ($config, $config_mt) = load_config($config_filename, $data);
68 0           my %processes = ();
69 0           while (@$config){
70 0           my $pause = 1;
71 0 0         ($config, $config_mt) = load_config($config_filename, $data) if ($config_mt < (stat $config_filename )[9]);
72 0 0         return if (!@$config);
73 0           for my $clear (<.taskflow.*.clear>) {
74 0           my $rule = substr($clear, 10, -6);
75 0           $log->info('clearing rule '.$rule);
76 0           delete $data->{$rule};
77 0           unlink($clear);
78             }
79 0           for my $cfg (@$config) {
80 0 0         next if (!defined $cfg);
81 0           my ($name, $pattern, $dt, $action, $ampersand) = @$cfg;
82 0           for my $filename (glob($pattern)) {
83 0 0         next if (!$filename);
84 0           my $mt = (stat $filename)[9];
85 0 0         next if ($mt > time - $dt);
86 0           my $pid_file = $filename.".$name.pid";
87 0           my $log_file = $filename.".$name.out";
88 0           my $err_file = $filename.".$name.err";
89 0           (my $key = $pattern.'='.$filename.':'.$action) =~ s/\s+/ /g;
90 0 0 0       unless (-e $pid_file or -e $err_file) {
91 0 0 0       if (!exists $data->{$key} or $data->{$key} != $mt){
92 0           (my $command = $action) =~ s/\Q$target_name\E/$filename/g;
93 0           $log->info($filename.' -> '.$command); my $buffer;
  0            
94             my $return;
95 0 0         if (my $pid = fork) {
96             # parent - child process pid is available in $pid
97 0 0         open my $fh, '>', $pid_file or die $!;
98 0           print $fh $pid; # write pid
99 0           close $fh;
100 0 0         waitpid($pid, 0) unless ($ampersand);
101             } else { # $pid is zero here if defined
102 0 0         die "cannot fork: $!" unless defined $pid;
103             # parent process pid is available with getppid
104 0           open STDOUT, '>', $log_file;
105 0           open STDERR, '>', $log_file;
106 0           $return = system $command;
107 0           close STDOUT;
108 0           close STDERR;
109             }
110 0           $processes{$pid_file} = [$filename, $command, $return];
111             }
112             }
113 0           my @pids = keys %processes;
114 0 0 0       if ($pid_file ~~ @pids and exists $processes{$pid_file}[2] and $processes{$pid_file}[2] == 0) {
    0 0        
      0        
115 0           my ($filename, $command, $return) = @{$processes{$pid_file}};
  0            
116 0 0         if ($return){
117 0 0         open my $fh, '>', $err_file or die $!;
118 0           print $fh $return;
119 0           close $fh;
120             }else{
121 0           $data->{$key} = $mt;
122 0 0         $data->{$name} = (defined $data->{$name}) ? $data->{$name}.' '.$key : $key;
123             }
124 0           delete $processes{$pid_file};
125 0           unlink $pid_file;
126 0           $pause = 0;
127             } elsif (-e $pid_file and $pid_file !~ @pids ) {
128 0           unlink $pid_file;
129 0           $pause = 0;
130             }
131             }
132 0 0         sleep $sleep if ($pause);
133             }
134             }
135             }
136              
137 0   0 0 1   sub version { my $ver = shift // $VERSION; print "Version: $ver\n"; exit 0; }
  0            
  0            
138 0     0 1   sub usage { system("perldoc $0"); exit 0; }
  0            
139             1; # End of App::Taskflow
140             __END__