File Coverage

blib/lib/Lim/Util.pm
Criterion Covered Total %
statement 36 238 15.1
branch 0 110 0.0
condition 0 24 0.0
subroutine 12 25 48.0
pod 11 11 100.0
total 59 408 14.4


line stmt bran cond sub pod time code
1             package Lim::Util;
2              
3 7     7   41 use common::sense;
  7         13  
  7         46  
4 7     7   303 use Carp;
  7         12  
  7         404  
5              
6 7     7   40 use Log::Log4perl ();
  7         13  
  7         120  
7 7     7   10359 use File::Temp ();
  7         182693  
  7         221  
8 7     7   70 use Fcntl qw(:seek);
  7         584  
  7         1144  
9 7     7   7092 use IO::File ();
  7         7688  
  7         180  
10 7     7   7652 use Digest::SHA ();
  7         30914  
  7         288  
11 7     7   70 use Scalar::Util qw(blessed);
  7         18  
  7         503  
12 7     7   1913 use URI::Escape ();
  7         3228  
  7         151  
13              
14 7     7   3553 use AnyEvent ();
  7         19073  
  7         499  
15 7     7   7825 use AnyEvent::Util ();
  7         120940  
  7         223  
16              
17 7     7   91 use Lim ();
  7         15  
  7         33514  
18              
19             =encoding utf8
20              
21             =head1 NAME
22              
23             Lim::Util - Utilities for plugins
24              
25             =head1 VERSION
26              
27             See L for version.
28              
29             =cut
30              
31             our $VERSION = $Lim::VERSION;
32             our %CALL_METHOD = (
33             Create => 'PUT',
34             Read => 'GET',
35             Update => 'POST',
36             Delete => 'DELETE'
37             );
38              
39             =head1 SYNOPSIS
40              
41             =over 4
42              
43             use Lim::Util;
44              
45             =back
46              
47             =head1 METHODS
48              
49             =over 4
50              
51             =item $full_path = Lim::Util::FileExists($file)
52              
53             Check if C<$file> exists by prefixing L->{prefix} and returns the
54             full path to the file or undef if it does not exist.
55              
56             =cut
57              
58             sub FileExists {
59 0     0 1   my ($file) = @_;
60            
61 0 0         if (defined $file) {
62 0           $file =~ s/^\///o;
63 0           foreach (@{Lim::Config->{prefix}}) {
  0            
64 0           my $real_file = $_.'/'.$file;
65            
66 0 0         if (-f $real_file) {
67 0           return $real_file;
68             }
69             }
70             }
71 0           return;
72             }
73              
74             =item $full_path = Lim::Util::FileReadable($file)
75              
76             Check if C<$file> exists by prefixing L->{prefix} and if it is
77             readable. Returns the full path to the file or undef if it does not exist.
78              
79             =cut
80              
81             sub FileReadable {
82 0     0 1   my ($file) = @_;
83            
84 0 0         if (defined $file) {
85 0           $file =~ s/^\///o;
86 0           foreach (@{Lim::Config->{prefix}}) {
  0            
87 0           my $real_file = $_.'/'.$file;
88            
89 0 0 0       if (-f $real_file and -r $real_file) {
90 0           return $real_file;
91             }
92             }
93             }
94 0           return;
95             }
96              
97             =item $full_path = Lim::Util::FileWritable($file)
98              
99             Check if C<$file> exists by prefixing L->{prefix} and if it is
100             writable. Returns the full path to the file or undef if it does not exist.
101              
102             =cut
103              
104             sub FileWritable {
105 0     0 1   my ($file) = @_;
106            
107 0 0         if (defined $file) {
108 0           $file =~ s/^\///o;
109 0           foreach (@{Lim::Config->{prefix}}) {
  0            
110 0           my $real_file = $_.'/'.$file;
111            
112 0 0 0       if (-f $real_file and -w $real_file) {
113 0           return $real_file;
114             }
115             }
116             }
117 0           return;
118             }
119              
120             =item $content = Lim::Util::FileReadContent($file)
121              
122             Read the file and return the content or undef if there was an error.
123              
124             =cut
125              
126             sub FileReadContent {
127 0     0 1   my ($file) = @_;
128            
129 0 0 0       if (-r $file and defined (my $fh = IO::File->new($file))) {
130 0           my ($tell, $content);
131 0           $fh->seek(0, SEEK_END);
132 0           $tell = $fh->tell;
133 0           $fh->seek(0, SEEK_SET);
134 0 0         if ($fh->read($content, $tell) == $tell) {
135 0           return $content;
136             }
137             }
138 0           return;
139             }
140              
141             =item [$temp_file] = Lim::Util::FileWriteContent([$file | $object,] $content)
142              
143             Write the content to a file or a new temporary file, content in file will be
144             reread and checked with a SHA checksum.
145              
146             If the C<$file> is specified, write the content to the filename and return 1 or
147             undef on error. Will overwrite the file if it exists.
148              
149             If the C<$object> is a L object, write the content to that file and
150             return 1 or undef on error.
151              
152             If no C<$file> or C<$object> is specified, write the content to a new temporary
153             file and return the L object or undef on error.
154              
155             =cut
156              
157             sub FileWriteContent {
158 0     0 1   my ($file, $content) = @_;
159 0           my $filename;
160            
161 0 0 0       if (defined $file and !defined $content) {
162 0           $content = $file;
163 0           undef($file);
164             }
165 0 0         if (blessed $file) {
    0          
166 0 0         unless ($file->isa('File::Temp')) {
167 0           return;
168             }
169 0           $filename = $file->filename;
170             }
171             elsif (defined $file) {
172 0           my $fh = IO::File->new;
173 0 0         unless ($fh->open($file, '>')) {
174 0           return;
175             }
176 0           $filename = $file;
177 0           $file = $fh;
178             }
179 0 0         unless (defined $content) {
180 0           return;
181             }
182 0 0         unless (defined $file) {
183 0           eval {
184 0           $file = File::Temp->new;
185             };
186 0 0         if ($@) {
187             # TODO log error
188 0           return;
189             }
190 0           $filename = $file->filename;
191             }
192              
193 0           print $file $content;
194 0           $file->flush;
195 0           $file->close;
196            
197 0           my $fh = IO::File->new;
198 0 0         if ($fh->open($filename)) {
199 0           my ($tell, $read);
200 0           $fh->seek(0, SEEK_END);
201 0           $tell = $fh->tell;
202 0           $fh->seek(0, SEEK_SET);
203 0 0         unless ($fh->read($read, $tell) == $tell) {
204 0           $fh->close;
205 0 0         unless ($file->isa('File::Temp')) {
206 0           unlink($filename);
207             }
208 0           return;
209             }
210 0 0         unless (Digest::SHA::sha1_base64($content) eq Digest::SHA::sha1_base64($read)) {
211 0           $fh->close;
212 0 0         unless ($file->isa('File::Temp')) {
213 0           unlink($filename);
214             }
215 0           return;
216             }
217             }
218 0 0         return $file->isa('File::Temp') ? $file : 1;
219             }
220              
221             =item $temp_file = Lim::Util::TempFile
222              
223             Creates a temporary file. Returns a L object or undef if there where
224             problems creating the temporary file.
225              
226             =cut
227              
228             sub TempFile {
229 0     0 1   my $tmp;
230            
231 0           eval {
232 0           $tmp = File::Temp->new;
233             };
234            
235 0 0         unless ($@) {
236             # TODO log error
237 0           return $tmp;
238             }
239 0           return;
240             }
241              
242             =item $temp_file = Lim::Util::TempFileLikeThis($file)
243              
244             Creates a temporary file that will have the same owner and mode as the specified
245             C<$file>. Returns a L object or undef if the specified file did not
246             exist or if there where problems creating the temporary file.
247              
248             =cut
249              
250             sub TempFileLikeThis {
251 0     0 1   my ($file) = @_;
252            
253 0 0 0       if (defined $file and -f $file) {
254 0           my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,
255             $atime,$mtime,$ctime,$blksize,$blocks)
256             = stat($file);
257 0           my $tmp;
258            
259 0           eval {
260 0           $tmp = File::Temp->new;
261             };
262            
263             # TODO log error
264            
265 0 0         unless ($@) {
266 0 0 0       if (chmod($mode, $tmp->filename) and chown($uid, $gid, $tmp->filename)) {
267 0           return $tmp;
268             }
269             }
270             }
271 0           return;
272             }
273              
274             =item ($method, $uri) = Lim::Util::URIize($call)
275              
276             Returns an URI based on the C<$call> given and the corresponding HTTP method to
277             be used.
278              
279             Example:
280              
281             =over 4
282              
283             use Lim::Util;
284             ($method, $uri) = Lim::Util::URIize('ReadVersion');
285             print "$method $ur\n";
286             ($method, $uri) = Lim::Util::URIize('CreateOtherCall');
287             print "$method $ur\n";
288              
289             =back
290              
291             Produces:
292              
293             =over 4
294              
295             GET /version
296             PUT /other_call
297              
298             =back
299              
300             =cut
301              
302             sub URIize {
303 0     0 1   my @parts = split(/([A-Z][^A-Z]*)/o, $_[0]);
304 0           my ($part, $method, $uri);
305            
306 0           while (scalar @parts) {
307 0           $part = shift(@parts);
308 0 0         if ($part ne '') {
309 0           last;
310             }
311             }
312            
313 0 0         unless (exists $CALL_METHOD{$part}) {
314 0           confess __PACKAGE__, ': No conversion found for ', $part, ' (', $_[0], ')';
315             }
316            
317 0           $method = $CALL_METHOD{$part};
318            
319 0           @parts = grep !/^$/o, @parts;
320 0 0         unless (scalar @parts) {
321 0           confess __PACKAGE__, ': Could not build URI (', $_[0], ')';
322             }
323 0           $uri = lc(join('_', @parts));
324            
325 0           return ($method, '/'.$uri);
326             }
327              
328             =item $hash_ref = Lim::Util::QueryDecode($query_string)
329              
330             Returns an HASH reference of the decode query string.
331              
332             =cut
333              
334             sub QueryDecode {
335 0     0 1   my ($href, $href_final) = ({}, {});
336            
337 0           foreach my $part (split(/&/o, $_[0])) {
338 0           my ($key, $value) = split(/=/o, $part, 2);
339              
340 0           $key = URI::Escape::uri_unescape($key);
341 0           $value = URI::Escape::uri_unescape($value);
342            
343 0 0         unless ($key) {
344 0           return;
345             }
346            
347             # check if last element is array and remove it from $key
348 0 0         my $array = $key =~ s/\[\]$//o ? 1 : 0;
349             # verify $key
350 0 0         unless ($key =~ /^[^\]]+(?:\[[^\]]+\])*$/o) {
351 0           return;
352             }
353             # remove last ] so we don't split or get it in $k
354 0           $key =~ s/\]$//o;
355              
356 0           my @keys = split(/(?:\]\[|\[)/o, $key);
357 0           my $this = $href;
358 0           while (defined (my $k = shift(@keys))) {
359 0 0         unless (scalar @keys) {
360 0 0 0       if ($array and exists $this->{$k}) {
361 0 0         unless (ref($this->{$k}) eq 'ARRAY') {
362 0           return;
363             }
364 0           push(@{$this->{$k}}, $value);
  0            
365 0           last;
366             }
367 0 0         $this->{$k} = $array ? [ $value ] : $value;
368 0           last;
369             }
370              
371 0 0         if (exists $this->{$k}) {
372 0           $this = $this->{$k};
373 0           next;
374             }
375            
376 0           $this = $this->{$k} = {};
377             }
378             }
379              
380             # restruct hashes with all numeric keys to arrays
381 0           my @process = ([$href, $href_final, undef, undef]);
382 0           while (defined (my $this = shift(@process))) {
383 0           my ($old, $new, $parent, $key) = @$this;
384            
385 0           my $numeric = 1;
386 0           foreach (keys %$old) {
387 0 0         unless (/^\d+$/o) {
388 0           $numeric = 0;
389 0           last;
390             }
391             }
392            
393 0 0         if ($numeric) {
394 0           my @array;
395 0           foreach (sort (keys %$old)) {
396 0 0         if (ref($old->{$_}) eq 'HASH') {
397 0           my $entry = {};
398 0           push(@array, $entry);
399 0           push(@process, [$old->{$_}, $entry, \@array, scalar @array - 1]);
400 0           next;
401             }
402 0           push(@array, $old->{$_});
403             }
404            
405 0 0         if (ref($parent) eq 'HASH') {
    0          
406 0           $parent->{$key} = \@array;
407             }
408             elsif (ref($parent) eq 'ARRAY') {
409 0           $parent->[$key] = \@array;
410             }
411             else {
412 0           return;
413             }
414             }
415             else {
416 0           foreach (keys %$old) {
417 0 0         if (ref($old->{$_}) eq 'HASH') {
418 0           $new->{$_} = {};
419 0           push(@process, [$old->{$_}, $new->{$_}, $new, $_]);
420 0           next;
421             }
422 0           $new->{$_} = $old->{$_};
423             }
424             }
425             }
426            
427 0           return $href_final;
428             }
429              
430             =item $camelized = Lim::Util::Camelize($underscore)
431              
432             Convert underscored text to camelized, used for translating URI to calls.
433              
434             Example:
435              
436             =over 4
437              
438             use Lim::Util;
439             print Lim::Util::Camelize('long_u_r_i_call_name'), "\n";
440              
441             =back
442              
443             Produces:
444              
445             =over 4
446              
447             LongURICallName
448              
449             =back
450              
451             =cut
452              
453             sub Camelize {
454 0     0 1   my ($underscore) = @_;
455 0           my $camelized;
456            
457 0           foreach (split(/_/o, $underscore)) {
458 0           $camelized .= ucfirst($_);
459             }
460            
461 0           return $camelized;
462             }
463              
464             =item [$cv =] Lim::Util::run_cmd $cmd, key => value...
465              
466             This function extends L with a timeout and will also
467             set C option.
468              
469             =over 4
470              
471             =item timeout => $seconds
472              
473             Creates a timeout for the running command and will try and kill it after the
474             specified C<$seconds>, see below how you can change the kill functionallity.
475              
476             Using C will set C<$$> option to L so you
477             won't be able to use that option.
478              
479             =item cb => $callback->($cv)
480              
481             This is required if you'r using C.
482              
483             Call the given C<$callback> when the command finish or have timed out with the
484             condition variable returned by L. If the command timed
485             out the condition variable will be set as if the command failed.
486              
487             =item kill_sig => 15
488              
489             Signal to use when trying to kill the command.
490              
491             =item kill_try => 3
492              
493             Number of times to try and kill the command with C.
494              
495             =item interval => 1
496              
497             Number of seconds to wait between each attempt to kill the command.
498              
499             =item kill_kill => 1
500              
501             If true (default) kill the command with signal KILL after trying to kill it with
502             C for the specified number of C attempts.
503              
504             =back
505              
506             =cut
507              
508             sub run_cmd {
509 0     0 1   my $cmd = shift;
510 0           my %args = (
511             kill_try => 3,
512             kill_kill => 1,
513             kill_sig => 15,
514             interval => 1,
515             @_
516             );
517 0           my ($pid, $timeout) = (0, undef);
518              
519 0           my %pass_args = %args;
520 0           foreach (qw(kill_try kill_kill timeout interval cb)) {
521 0           delete $pass_args{$_};
522             }
523 0           $pass_args{close_all} = 1;
524            
525 0 0         if (exists $args{timeout}) {
526 0           $pass_args{'$$'} = \$pid;
527              
528 0 0 0       unless (exists $args{cb} and ref($args{cb}) eq 'CODE') {
529 0           confess __PACKAGE__, ': must have cb with timeout or invalid';
530             }
531            
532 0 0         unless ($args{timeout} > 0) {
533 0           confess __PACKAGE__, ': timeout invalid';
534             }
535              
536 0 0         unless ($args{interval} > 0) {
537 0           confess __PACKAGE__, ': interval invalid';
538             }
539            
540 0 0         unless ($args{kill_try} >= 0) {
541 0           confess __PACKAGE__, ': kill_try invalid';
542             }
543            
544             $timeout = AnyEvent->timer(
545             after => $args{timeout},
546             interval => $args{interval},
547             cb => sub {
548 0 0   0     unless ($pid) {
549 0           undef($timeout);
550 0           return;
551             }
552            
553 0 0         if ($args{kill_try}--) {
554 0           kill($args{kill_sig}, $pid);
555             }
556             else {
557 0 0         if ($args{kill_kill}) {
558 0           kill(9, $pid);
559             }
560 0           undef($timeout);
561             }
562 0           });
563              
564 0 0         Lim::DEBUG and Log::Log4perl->get_logger->debug('run_cmd [timeout ', $args{timeout},'] ', (ref($cmd) eq 'ARRAY' ? join(' ', @$cmd) : $cmd));
    0          
565              
566 0           my $cv = AnyEvent::Util::run_cmd
567             $cmd,
568             %pass_args;
569             $cv->cb(sub {
570 0     0     undef($timeout);
571 0           $args{cb}->(@_);
572 0           });
573 0           return;
574             }
575            
576 0 0         Lim::DEBUG and Log::Log4perl->get_logger->debug('run_cmd ', (ref($cmd) eq 'ARRAY' ? join(' ', @$cmd) : $cmd));
    0          
577              
578 0           return AnyEvent::Util::run_cmd
579             $cmd,
580             %pass_args;
581             }
582              
583             =back
584              
585             =head1 AUTHOR
586              
587             Jerry Lundström, C<< >>
588              
589             =head1 BUGS
590              
591             Please report any bugs or feature requests to L.
592              
593             =head1 SUPPORT
594              
595             You can find documentation for this module with the perldoc command.
596              
597             perldoc Lim::Util
598              
599             You can also look for information at:
600              
601             =over 4
602              
603             =item * Lim issue tracker (report bugs here)
604              
605             L
606              
607             =back
608              
609             =head1 ACKNOWLEDGEMENTS
610              
611             =head1 LICENSE AND COPYRIGHT
612              
613             Copyright 2012-2013 Jerry Lundström.
614              
615             This program is free software; you can redistribute it and/or modify it
616             under the terms of either: the GNU General Public License as published
617             by the Free Software Foundation; or the Artistic License.
618              
619             See http://dev.perl.org/licenses/ for more information.
620              
621              
622             =cut
623              
624             1; # End of Lim::Util