Subversion Repositories DevTools

Rev

Rev 271 | Rev 275 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
271 dpurdie 1
########################################################################
2
# Copyright (C) 2008 ERG Limited, All rights reserved
3
#
4
# Module name   : JatsCopy
5
# Module type   : Makefile system
6
# Compiler(s)   : Perl
7
# Environment(s): jats
8
#
9
#                 Utility functions to
10
#                   CopyDir                 - Copy Dir Tree
11
#                   CopyFile                - Copy one file
12
#                   CreateDir               - Create one directory
13
#                   DeleteDir               - Delete Dir Tree
14
#                   DeleteFile              - Delete a file
15
#                   SetCopyDirDefaults      - Set application wide defaults
16
#
17
#                 Performs these operations within a common logging
18
#                 and error reporting framework
19
#
20
#                 Intended to replace all similar lumps of code
21
#                 within JATS
22
#
23
#                 Intended to make life simple
24
#
25
#                 It has  alot of callbacks and config, but the body
26
#                 of the work is similar
27
#
28
# Examples:
29
#               CopyDir ( 'aaa', 'bbb' );
30
#               Simply copy the aaa dir-tree to 'bbb'
31
#
32
#               CopyDir ( 'aaa', 'bbb',
33
#                           { 'DeleteFirst' => 1,
34
#                             'Error' => \&MyError,
35
#                             'Logger' => \&MyLog,
36
#                             'Examine' => \&MyExamine,
37
#                             'Expert' => \&MyExpert,
38
#                             'Exists' => \&MyExists,
39
#                             'Ignore' => ['a2', 'build.pl'],
40
#                             'IgnoreRE' => ['2$'],
41
#                             'IgnoreDirs' => ['.svn'],
42
#                             });
43
#
44
#              Complex copy of the 'aaa' tree to 'bbb'
45
#               Do my own error processing
46
#               Do my own loggind
47
#               Examine each file/dir being processed
48
#               Do my own copy ( Expert)
49
#               Notify me if an existing file is present
50
#               Do not copy files called a2 and build.pl
51
#               Do not copy the .svn subdir
52
#               Do not copy files ending in 2
53
#
273 dpurdie 54
# Issues:
55
#               MatchDirs and MatchDirsRE don't work
56
#               Need to define what is wanted first
57
#               IgnoreDirs - appears to be useful
58
#
271 dpurdie 59
#......................................................................#
60
 
61
use strict;
62
use warnings;
63
 
64
package JatsCopy;
65
 
66
use JatsError;
67
use FileUtils;
68
use File::Path;
69
 
70
 
71
# automatically export what we need into namespace of caller.
72
use Exporter();
73
our (@ISA, @EXPORT);
74
@ISA         = qw(Exporter);
75
@EXPORT      = qw(
76
                    CopyDir
77
                    CopyFile
78
                    CreateDir
79
                    DeleteDir
80
                    DeleteFile
81
                    SetCopyDirDefaults
82
                );
83
 
84
#
85
#   Global Data
86
#
87
my $Global_opts;
88
 
89
#-------------------------------------------------------------------------------
90
# Function        : CopyDir
91
#
92
# Description     : Utility function to copy a directory of files
93
#                   This function is NOT reentrant
94
#                   Do not use it within callback frunctions
95
#
96
# Inputs          : $src_dir                - Src directory
97
#                   $dst_dir                - Dest dir
98
#                   $opt                    - An Array or Hash Ref of options
99
#                    Flags that affect operation
100
#                       DeleteFirst     - True: Delete target directory first
101
#                       NoSubDirs       - True: Only source directory files
102
#                       Flatten         - True: Flatten output dir
103
#                       Log             - True: Log activity
104
#                       EmptyDirs       - True: Create empty dirs
105
#                       IgnoreDots      - True: Ignore files and dirs starting with .
106
#                       NoOverwrite     - True: Do not replace file in target
107
#                       DuplicateLinks  - True: Duplicate, don't copy links
108
#                       SymlinkFiles    - True: Create symlinks if possible
109
#                       ReadOnlyFiles   - True: Make files Read Only
273 dpurdie 110
#                       KeepSrcTail     - True: Keeps the tail of the source dir
271 dpurdie 111
#                     User callback functions
112
#                       Expert          - Function to do the work
113
#                       Examine         - Function to examine each entry
114
#                                         If True, then proceed
115
#                       Exists          - Function called if target file exists
116
#                                         If True, then delete and replace
117
#                       Logger          - Function to Log operations
118
#                       Error           - Function to Process errors
119
#                     Mataching operations
120
#                       Ignore          - An array of files to ignore
121
#                       IgnoreRE        - An array of files to ignore (RE)
122
#                       IgnoreDirs      - An array of subdirs to ignore
123
#                       IgnoreDirsRE    - An array of subdirs to ignore (RE)
124
#                       Match           - An array of files to match
125
#                       MatchRE         - An array of file to match (RE)
273 dpurdie 126
#                       MatchDirs       - An array of dirs to match
127
#                       MatchDirsRE     - An array of dirs to match (RE)
271 dpurdie 128
#                     Misc
129
#                       Stats           - A ref to a hash of stats
273 dpurdie 130
#                       FileList        - Ref to array of target files
271 dpurdie 131
#                       UserData        - Item passed through to call backs
132
#                                         for the users own purposes.
133
#
134
#                   File and dir match/ignore operations come in two flavours
135
#                   Simple : Use of simple wildcards: ?,* and [...] constructs
136
#                   RE     : Full regular expression
137
#
138
#                   Dir match/ignore work on a single dirname, not dirs and subdirs.
139
#
140
#                   Matches rules are applied before ignore rules.
141
#
142
#                   User functions are called a ref to a copy of the options hash
143
#                   with the folling data added.
144
#                       item            - Source Path
145
#                       file            - Filename
146
#                       target          - Dest Path
147
#                   In the 'Examine' callback, 'target' may be modified
148
#                   This will be used as the path to target file.
149
#
150
# Returns         :
151
#
152
sub CopyDir
153
{
154
    my $src_dir = shift;
155
    my $dst_dir = shift;
156
 
157
    #
158
    #   Setup default options
159
    #   Merge user options with default options to create a local set
160
    #
161
    my $opt = JatsCopyInternal::DefaultOpts( 'CopyDir', @_);
162
 
163
    #
164
    #   Remove any training / from the users dirs
165
    #
166
    $src_dir =~ s~/+$~~;
167
    $dst_dir =~ s~/+$~~;
168
 
169
    #
273 dpurdie 170
    #   Keep some fo the source directory
171
    #
172
    $dst_dir .= '/' . StripDir($src_dir)
173
        if ( $opt->{'KeepSrcTail'} );
174
 
175
    #
271 dpurdie 176
    #   Insert some default options
177
    #
178
    $opt->{'SrcDir'} = $src_dir;
179
    $opt->{'DstDir'} = $dst_dir;
180
 
181
    #
182
    #   Convert Match requests into MatchRE requests
183
    #
184
    if ($opt->{'OptMatch'} )
185
    {
186
        JatsCopyInternal::Pat2GlobList ($opt ,'Match'     , 'MatchRE' );
187
        JatsCopyInternal::Pat2GlobList ($opt ,'MatchDirs' , 'MatchDirsRE' );
188
        JatsCopyInternal::Pat2GlobList ($opt ,'Ignore'    , 'IgnoreRE' );
189
        JatsCopyInternal::Pat2GlobList ($opt ,'IgnoreDirs', 'IgnoreDirsRE' );
190
    }
191
 
192
    #
193
    #   Validate source dir
194
    #
195
    Error ("CopyDir: Source dir not found: $src_dir" )
196
        if ( ! -d $src_dir );
197
 
198
    #
199
    #   Delete and create target dir
200
    #
201
    rmtree( $dst_dir )
202
        if ( $opt->{'DeleteFirst'} );
273 dpurdie 203
    JatsCopyInternal::CreateDir ( $dst_dir, $opt );
271 dpurdie 204
 
205
    #
206
    #   Invoke Find to decend the directory tree
207
    #
208
    #   Have used global vars to pass data into the find callback
209
    #
210
    #   Only use the preprocess rotine if we are doing any form
211
    #   of matching.
212
    #
213
    #   'follow_fast' does not work correctly under windows if the path
214
    #   has a drive letter. Don't use it under Windows
215
    #
216
 
217
    JatsCopyInternal::MyFind( $opt );
218
 
219
#    DebugDumpData("opt", $opt );
220
}
221
 
222
#-------------------------------------------------------------------------------
223
# Function        : CopyFile
224
#
225
# Description     : Utility function to copy a single file
226
#                   Uses the same options and loging infrastructure
227
#                   as CopyDir
228
#
229
# Inputs          : $src_file               - Src directory
230
#                   $dst_file               - Dest file (or dir)
231
#                   $opt                    - A Hash of options
232
#                                             Refer to CopyDir
233
#
273 dpurdie 234
# Returns         : Path to the target file
271 dpurdie 235
#
236
sub CopyFile
237
{
238
    my $src_file = shift;
239
    my $dst_file = shift;
240
    my $opt = JatsCopyInternal::DefaultOpts( 'CopyFile', @_);
241
 
242
    #
243
    #   Do not Validate source dir
244
    #   Do it within the copy operation and get the same error
245
    #   handling as the CopyDir processing
246
    #
247
 
248
    #
273 dpurdie 249
    #   If the target is a directory, then copy by name
250
    #
251
    my $file = StripDir ($src_file);
252
    $dst_file .= '/' . $file if ( -d $dst_file );
253
 
254
    #
271 dpurdie 255
    #   Insert additional options to provide the same interface to
256
    #   the internal functions used to do the copy
257
    #
258
    #       item            - Source Path
259
    #       tgt             - Dest subdir below dst_dir
260
    #       file            - Filename
261
    #       target          - Dest Path
262
    #
263
    $opt->{'item'}     = $src_file;
273 dpurdie 264
    $opt->{'file'}     = $file;
271 dpurdie 265
    $opt->{'target'}   = $dst_file;
266
    $opt->{'type'}     = 'f';
267
 
268
    #
269
    #   Invoke the common file copy routine
270
    #
273 dpurdie 271
    return JatsCopyInternal::CopyFile ( $src_file, $dst_file, $opt );
271 dpurdie 272
}
273
 
274
#-------------------------------------------------------------------------------
275
# Function        : CreateDir
276
#
277
# Description     : Utility function to create a directory
278
#                   Uses the same options and loging infrastructure
279
#                   as CopyDir
280
#
281
# Inputs          : $dst                    - Dest dir to create
282
#                   $opt                    - A Hash of options
283
#                                             Refer to CopyDir
284
#
285
# Returns         : 
286
#
287
sub CreateDir
288
{
289
    my $dst = shift;
290
    my $opt = JatsCopyInternal::DefaultOpts( 'CreateDir', @_ );
291
 
292
    #
293
    #   Insert additional options to provide the same interface to
294
    #   the internal functions used to do the copy
295
    #
296
    #       item            - Source Path
297
    #       tgt             - Dest subdir below dst_dir
298
    #       file            - Filename
299
    #       target          - Dest Path
300
    #
301
    $opt->{'item'}     = '';
302
    $opt->{'file'}     = '';
303
    $opt->{'target'}   = $dst;
304
    $opt->{'type'}     = 'd';
305
 
306
    #
273 dpurdie 307
    #   Delete and create target dir
308
    #
309
    if ( $opt->{'DeleteFirst'} && -e $dst )
310
    {
311
        $opt->{'Logger'} ( "Delete Dir" ,$opt );
312
        rmtree( $dst );
313
    }
314
 
315
    #
271 dpurdie 316
    #   Invoke the common file copy routine
317
    #
318
    JatsCopyInternal::CreateDir ( $dst, $opt );
319
 
320
#    DebugDumpData("opt", $opt );
321
}
322
 
323
#-------------------------------------------------------------------------------
324
# Function        : DeleteDir
325
#
326
# Description     : Utility function to delete a directory tree
327
#                   Uses the same options and loging infrastructure
328
#                   as CopyDir
329
#
330
# Inputs          : $dst                    - Dest dir to create
331
#                   $opt                    - A Hash of options
332
#                                             Refer to CopyDir
333
#
334
# Returns         : 
335
#
336
sub DeleteDir
337
{
338
    my $dst = shift;
339
    my $opt = JatsCopyInternal::DefaultOpts( 'DeleteDir', @_ );
340
 
341
    #
342
    #   Insert additional options to provide the same interface to
343
    #   the internal functions used to do the copy
344
    #
345
    #       item            - Source Path
346
    #       tgt             - Dest subdir below dst_dir
347
    #       file            - Filename
348
    #       target          - Dest Path
349
    #
350
    $opt->{'item'}     = '';
351
    $opt->{'file'}     = '';
352
    $opt->{'target'}   = $dst;
353
    $opt->{'type'}     = 'd';
354
 
355
    #
356
    #   Invoke the common file copy routine
357
    #
358
    $opt->{'Logger'} ( "Delete Dir" ,$opt );
359
    rmtree( $dst );
360
}
361
 
362
#-------------------------------------------------------------------------------
363
# Function        : DeleteFile
364
#
365
# Description     : Utility function to delete a file
366
#                   Uses the same options and loging infrastructure
367
#                   as CopyDir
368
#
369
#                   Uses the same functions as DeleteDir simply because
370
#                   rmtree does such a great job
371
#
372
# Inputs          : $dst                    - Dest dir to create
373
#                   $opt                    - A Hash of options
374
#                                             Refer to CopyDir
375
#
376
# Returns         : 
377
#
378
sub DeleteFile
379
{
380
    my $dst = shift;
381
    my $opt = JatsCopyInternal::DefaultOpts( 'DeleteFile', @_);
382
 
383
    #
384
    #   Insert additional options to provide the same interface to
385
    #   the internal functions used to do the copy
386
    #
387
    #       item            - Source Path
388
    #       tgt             - Dest subdir below dst_dir
389
    #       file            - Filename
390
    #       target          - Dest Path
391
    #
392
    $opt->{'item'}     = '';
393
    $opt->{'file'}     = '';
394
    $opt->{'target'}   = $dst;
395
    $opt->{'type'}     = 'f';
396
 
397
    #
398
    #   Invoke the common file copy routine
399
    #
400
    $opt->{'Logger'} ( "Delete File" ,$opt );
401
    rmtree( $dst );
402
}
403
 
404
#-------------------------------------------------------------------------------
405
# Function        : SetCopyDirDefaults
406
#
407
# Description     : Set default options to be used by all the functions
408
#                   Simplifies the process fo setting options on all
409
#                   operations
410
#
411
# Inputs          : $uopt                        - A Hash of options
412
#                                                - An array of options
413
#
414
# Returns         : Nothing
415
#
416
sub SetCopyDirDefaults
417
{
418
    my $name = 'SetCopyDirDefaults';
419
    return if ( $#_ < 0);
420
 
421
    #
422
    #   User can pass in a reference to a hash or a hash as
423
    #   a list of argumnts
424
    #
425
    my $uopt = JatsCopyInternal::ArgsToRef ($name, @_ );
426
 
427
    #
428
    #   Insert user options into the default hash
429
    #
430
    JatsCopyInternal::ValidateArg ($name, $uopt, $Global_opts );
431
 
432
    #
433
    #   BEGIN Block to initialise default global options
434
    #   Note: This will be called first
435
    #   Note: Multiple begin blocks are allowed
436
    #
437
    sub BEGIN
438
    {
439
        my %stats;
440
        #
441
        #   Insert some default options
442
        #   Later insert user options
443
        #
444
        $Global_opts->{'Error'}  = \&JatsCopyInternal::Error;
445
        $Global_opts->{'Expert'} = \&JatsCopyInternal::Body;
446
        $Global_opts->{'Logger'} = \&JatsCopyInternal::Log;
447
        $Global_opts->{'Stats'}  = \%stats;
448
    }
449
}
450
 
451
################################################################################
452
#
453
#   Hide the body of the work within another package
454
#   Done to make it obvious which parts are user accessible
455
#
456
package JatsCopyInternal;
457
 
458
#use JatsError;
459
use File::Basename;
460
use File::Path;
461
use File::Copy;
462
use Cwd 'abs_path';
463
 
464
#-------------------------------------------------------------------------------
465
# Function        : MyFind
466
#
467
# Description     : Recurse a directory tree and locate files of interest
468
#
469
#                   Tried to use File::Find, but this has several major
470
#                   limitations:
471
#                       'preprocess' does not work with 'follow' symlinks
472
#                       Without 'preprocess' there is no way to terminate
473
#                       a directory sub-tree recursion leading to complicated
474
#                       code to do directory pruning.
475
#
476
#                   This function will perform file and directory name matching
477
#                   on the fly. All items with match are passed to the user
478
#                   examination functions and eventually to the processing
479
#                   function to perform the actual copy
480
#
481
#                   Current implementation will:
482
#                       Follow dir Symlinks
483
#
484
#                       Process dir element anytime before the dir contents
485
#                       Not 'just' before.
486
#
487
#
488
# Inputs          : $opt                        - Hash of search options
489
#
490
# Returns         : Nothing
491
#
492
sub MyFind
493
{
494
    my ( $opt ) = @_;
495
    local ( *DIR );
496
 
497
    #
498
    #   Create a list of subdirs to scan
499
    #       Elements do not contain the SrcDir
500
    #       Elements have a '/' suffix - simplify joining
501
    #
502
    my @dirs = '';
503
 
504
    #
505
    #   Process all directories in the list
506
    #   Pop them off so we do a depth first search
507
    #
508
    while ( @dirs )
509
    {
510
        my $root = pop( @dirs );
511
 
512
        my $dir = $opt->{'SrcDir'} . '/' . $root;
513
	    unless (opendir DIR, $dir )
514
        {
515
	        ::Warning ("File Find. Can't opendir($dir): $!\n");
516
	        next;
517
        }
518
	    my @filenames = readdir DIR;
519
	    closedir(DIR);
520
 
521
        foreach my $file ( @filenames )
522
        {
523
            #
524
            #   Ignore filesystem house keeping directories
525
            #
526
            next if ( $file eq '.' || $file eq '..' );
527
 
528
            #
529
            #   Common processing
530
            #       Ignore all files and directories that start with a .
531
            #       Unix 'hidden' files may be simply ignored
532
            #
533
            next if ( $opt->{'IgnoreDots'} && substr( $file, 0, 1) eq '.' );
534
 
535
            #
536
            #   Determine the type of element
537
            #       1)Link
538
            #           - Link to a File
539
            #           - Link to a directory
540
            #       2)File
541
            #       3)Directory
542
            #
543
            my $filename = $dir . $file;
544
            my $relname = $root . $file;
545
 
546
            #
547
            #   Stat the file
548
            #   Use speed trick. (-f _) will use into from last stat/lstat
549
            #
550
            stat ( $filename );
551
            if ( -f _ )
552
            {
553
                $opt->{'Stats'}{'examinedFiles'}++;
554
                next unless doMatch ( $file, $opt, 'MatchRE', 'IgnoreRE' );
555
                $opt->{'type'} = 'f';
556
            }
557
            elsif ( -d _ )
558
            {
559
                #
560
                #   Only process the top-level directory
561
                #
562
                next if ( $opt->{'NoSubDirs'} );
563
 
564
                #
565
                #   Match against wanted items
566
                #
567
                $opt->{'Stats'}{'examinedDirs'}++;
568
                next unless doMatch ( $file, $opt, 'MatchDirsRE', 'IgnoreDirsRE' );
569
 
570
                #
571
                #   Add to the list of future dirs to process
572
                #   Place on end to ensure depth first
573
                #   Algorithm requires dirname has a trailing /
574
                #
575
                push @dirs, $relname . '/';
576
 
577
                #
578
                #   Create flat output dir - no more processing
579
                #
580
                next if ( $opt->{'Flatten'} );
581
                $opt->{'type'} = 'd';
582
 
583
            }
584
            else
585
            {
586
                ::Warning ("Find File: Unknown type skipped: $filename");
587
                next;
588
            }
589
 
590
            #
591
            #   Have a valid element to process
592
            #   Setup parameters for later users
593
            #
594
            my $target = ( $opt->{'Flatten'} ) ? $file : $relname;
595
 
596
            $opt->{'file'}   = $file;                           # Element name
597
            $opt->{'item'}   = $filename;                       # Full path
598
            $opt->{'target'} = $opt->{'DstDir'} . '/' .$target; # Target(Below dest)
599
 
600
            #
601
            #   If the user has opted to examine each file then ...
602
            #   If user returns TRUE then continue with operation
603
            #
604
            #   Note: It is allowed to play with the copy args
605
            #         but be careful. Only 'target' should be messed with
606
            #
607
            if ( $opt->{'Examine'} )
608
            {
609
                next unless ( $opt->{'Examine'} ( $opt ) )
610
            }
611
 
612
            #
613
            #   Always invoke the 'Expert' function
614
            #   A dummy one will be provided unless the user gave one
615
            #
616
            $opt->{'Expert'} ( $opt );
617
        }
618
    }
619
}
620
 
621
#-------------------------------------------------------------------------------
622
# Function        : Body
623
#
624
# Description     : Default CopyDir copy operation function
625
#                   This function will be used if the user does not provide
626
#                   one of their own
627
#
628
# Inputs          : $opt            - Ref to hash of options and args
629
#
630
# Returns         :
631
#
632
sub Body
633
{
634
    my ($opt) = @_;
635
    my $item = $opt->{'item'};
636
    my $target = $opt->{'target'};
637
 
638
    #
639
    #   If a directory, create the directory
640
    #
641
    if ( $opt->{'type'} eq 'd' )
642
    {
643
        $opt->{'Stats'}{'dirs'}++;
644
 
645
        #
646
        #   Directories are handled differently
647
        #       - Directories are created with nice permissions
648
        #       - Empty directories are created here
649
        #
650
        if ( $opt->{'EmptyDirs'} )
651
        {
652
            CreateDir ($target, $opt);
653
        }
654
    }
655
    else
656
    {
657
        CopyFile ( $item, $target, $opt );
658
    }
659
}
660
 
661
#-------------------------------------------------------------------------------
662
# Function        : CreateDir
663
#
664
# Description     : Create a directory
665
#                   With loging
666
#
667
# Inputs          : $dir                        - Dir to Create
668
#                   $opt                        - Process Data
669
#
670
# Returns         : 
671
#
672
sub CreateDir
673
{
674
    my ($dir, $opt) = @_;
675
    if ( ! -d $dir )
676
    {
677
        $opt->{'Logger'} ( "Creating Dir", $opt, $dir );
678
        mkpath($dir, 0, 0775);
273 dpurdie 679
        $opt->{'Error'} ( "Failed to create dir [$dir]: $!", $! , $opt )
680
            unless( -d $dir );
681
 
271 dpurdie 682
    }
683
}
684
 
685
#-------------------------------------------------------------------------------
686
# Function        : CopyFile
687
#
688
# Description     : Copy a file with common logging and other basic options
689
#
690
# Inputs          : $item                      - Source Path
691
#                   $target                    - Dest Path (dir+name)
692
#                   $opt                       - Ref to options hash
693
#
694
#
695
#                   Only a few of the options are implemented
696
#                   Becareful if using this function directly
697
#
273 dpurdie 698
# Returns         : The path of the target file
271 dpurdie 699
#
700
sub CopyFile
701
{
702
    my ($item, $target, $opt) = @_;
703
 
704
    #
705
    #   If the target already exists then we may need to take some
706
    #   action. The default action is to delete and replace
707
    #
708
    if ( -e $target )
709
    {
710
        if ( $opt->{'Exists'} )
711
        {
273 dpurdie 712
            return $target unless
271 dpurdie 713
                $opt->{'Exists'} ( $opt );
714
        }
715
        elsif ( $opt->{'NoOverwrite'} )
716
        {
273 dpurdie 717
            return $target;
271 dpurdie 718
        }
719
        rmtree( $target );
720
    }
721
 
722
    #
723
    #   Ensure that the target directory exists
724
    #   Don't assume prior creation - the user may have messed with the path
725
    #
726
    my $tdir = $target;
727
    $tdir =~ s~/[^/]+$~~;
728
    CreateDir ( $tdir, $opt);
729
 
273 dpurdie 730
    #
731
    #   If the target is a 'broken' link then we will have got this
732
    #   far. It wan't have been reported as existing
733
    #
734
    unlink $target
735
        if ( -l $target );
736
 
737
    #
738
    #   Save name of target file
739
    #
740
    if ( defined $opt->{'FileList'} )
271 dpurdie 741
    {
273 dpurdie 742
        push @{$opt->{'FileList'}}, $target;
743
    }
744
 
745
    {
271 dpurdie 746
        #
747
        #   Try a symlink first
748
        #
749
        if ( $opt->{'SymlinkFiles'}  )
750
        {
751
            $opt->{'Logger'} ( "Linking File" ,$opt );
752
            if (symlink (abs_path( $item ), $target)  )
753
            {
754
                $opt->{'Stats'}{'links'}++;
755
                last;
756
            }
757
            #
758
            #   Symlink has failed
759
            #   Flag: Don't attempt to symlink anymore
760
            #
761
            $opt->{'SymlinkFiles'}  = 0;
762
        }
763
 
764
        #
765
        #   Copy file to destination
766
        #   If the file is a link, then duplicate the link contents
767
        #   Use: Unix libraries are created as two files:
768
        #        lib.xxxx.so -> libxxxx.so.vv.vv.vv
769
        #
770
        if ( -l $item && $opt->{'DuplicateLinks'} )
771
        {
772
            $opt->{'Logger'} ( "Copying Link" ,$opt );
773
            my $link = readlink $item;
774
            symlink ($link, $target );
775
            unless ( $link && -l $target )
776
            {
777
                $opt->{'Error'} ( "Failed to copy link [$item] to [$target]: $!", $! , $opt );
778
            }
779
            $opt->{'Stats'}{'links'}++;
780
            last;
781
        }
782
 
783
        if (File::Copy::copy($item, $target))
784
        {
785
            $opt->{'Logger'} ( "Copying File" ,$opt );
786
            my $perm = 0775;
787
            $perm = (stat $target)[2] & 07777 & 0555
788
                if ( $opt->{'ReadOnlyFiles'} );
789
            CORE::chmod $perm, $target;
790
            $opt->{'Stats'}{'files'}++;
791
            last;
792
        }
793
 
794
        #
795
        #   All attempts to copy have failed
796
        #
797
        $opt->{'Error'} ( "Failed to copy file [$item] to [$target]: $!", $! ,$opt );
798
    }
273 dpurdie 799
 
800
    return $target;
271 dpurdie 801
}
802
 
803
 
804
#-------------------------------------------------------------------------------
805
# Function        : Log
806
#
807
# Description     : Default Copy Log callback function
808
#
809
# Inputs          : $type
810
#                   $opt hash
811
#                   $ltarget                        - Target to log
812
#
813
# Returns         : 
814
#
815
sub Log
816
{
817
    my ($type, $opt, $ltarget) = @_;
818
    return unless ( $opt->{'Log'} );
819
 
820
    #
821
    #   User target or logging target as overide
822
    #
823
    $ltarget = $opt->{'target'} unless ( $ltarget );
824
 
825
    if ( $opt->{'Log'} < 2 )
826
    {
827
        JatsError::Information (sprintf( "%-15s [%s]", $type, $ltarget));
828
    }
829
    else
830
    {
831
        JatsError::Information (sprintf( "%-15s  [%s]->[%s], %s, %s", $type,
832
                                    $opt->{'item'},
833
                                    $ltarget,
834
                                    $opt->{'file'},
835
                                    $opt->{'type'},
836
                                    ));
837
    }
838
}
839
 
840
#-------------------------------------------------------------------------------
841
# Function        : Error
842
#
843
# Description     : Default Copy Error callback function
844
#
845
# Inputs          : $message
846
#                   $ecode
847
#                   $opt hash
848
#
849
# Returns         : Does not return
850
#
851
sub Error
852
{
853
    my ($message, $ecode, $opt) = @_;
854
    JatsError::Error ($message);
855
}
856
 
857
#-------------------------------------------------------------------------------
858
# Function        : Pat2GlobList
859
#
860
# Description     : Convert a list of simple filenames into list of
861
#                   RE. Simple filenames may contain simple globs
862
#
863
# Inputs          : $opt                - Option hash
864
#                   $src                - Name of Source Data
865
#                   $dst                - Name of Dest Data
866
#
867
# Returns         : Updates dst data
868
#
869
sub Pat2GlobList
870
{
871
    my ($opt, $src, $dst) = @_;
872
    foreach ( @{$opt->{$src}} )
873
    {
874
        push @{$opt->{$dst}}, glob2pat($_);
875
    }
876
}
877
 
878
#-------------------------------------------------------------------------------
879
# Function        : glob2pat
880
#
881
# Description     : Convert four shell wildcard characters into their equivalent
882
#                   regular expression; all other characters are quoted to
883
#                   render them literals.
884
#
885
# Inputs          : Shell style wildcard pattern
886
#
887
# Returns         : Perl RE
888
#
889
sub glob2pat
890
{
891
    my $globstr = shift;
892
    $globstr =~ s~^/~~;
893
    my %patmap = (
894
        '*' => '.*',
895
        '?' => '.',
896
        '[' => '[',
897
        ']' => ']',
273 dpurdie 898
        '-' => '-'
271 dpurdie 899
    );
900
    $globstr =~ s{(.)} { $patmap{$1} || "\Q$1" }ge;
901
    return '^' . $globstr . '$';
902
}
903
 
904
#-------------------------------------------------------------------------------
905
# Function        : doMatch
906
#
907
# Description     : Match a file against a match specification
908
#                   Match, before ignore
909
#
910
# Inputs          : $file                   - File to match
911
#                   $opt                    - Options hash
912
#                   $mname                  - RE to match
913
#                   $iname                  - RE's to ignore
914
#
915
# Returns         : true    - File can matched
916
#
917
sub doMatch
918
{
919
    my ($file, $opt, $mname, $iname) = @_;
920
 
921
    if ( $opt->{$mname} )
922
    {
923
        if ( my @mlist = @{$opt->{$mname}} )
924
        {
925
            #
926
            #   Must match if we have a match list,
927
            #   then process ignore list
928
            #
929
            my $match = 0;
930
            foreach (@mlist)
931
            {
932
                if ( $file =~ m~$_~ )
933
                {
934
                    $match = 1;
935
                    last;
936
                }
937
            }
938
            return 0
939
                unless ( $match );
940
        }
941
    }
942
 
943
    if ( $opt->{$iname} )
944
    {
945
        foreach ( @{$opt->{$iname}})
946
        {
947
            return 0
948
                if ( $file =~ m~$_~ );
949
        }
950
    }
951
    return 1;
952
}
953
 
954
 
955
#-------------------------------------------------------------------------------
956
# Function        : DefaultOpts
957
#
958
# Description     : Insert default opts into the option structure
959
#
960
# Inputs          : $name               - Utility name
961
#                   @opts               - User options (hash ref or an array)
962
#
963
# Returns         : Ref to a new set of options
964
#                   With defauls inserted
965
#
966
sub DefaultOpts
967
{
968
    my $name = shift;
969
    my $uopt = ArgsToRef ($name, @_ );
970
    my $opt;
971
 
972
    #
973
    #   Init with global options
974
    #
975
    foreach ( keys %{$Global_opts} )
976
    {
977
        $opt->{$_} = $Global_opts->{$_};
978
    }
979
 
980
    #
981
    #   Transfer the users options into our own working hash
982
    #   Allows the user to create an option-set that won't get messed with
983
    #   Validity test of user args
984
    #
985
    ValidateArg ($name, $uopt, $opt);
986
 
987
    #
988
    #   Determine if the underlying system supports symlinks
989
    #   May be killed later if we discover that the filesystem
990
    #   does not support symlinks
991
    #
992
    if ( $opt->{'SymlinkFiles'} )
993
    {
994
        my $symlinks = eval { symlink("",""); 1 } || 0;
995
        $opt->{'SymlinkFiles'} = 0
996
            unless ( $symlinks  );
997
    }
998
 
999
    #
1000
    #   Return a new options structure
1001
    #   One that won't pollute the users set of options
1002
    #
1003
    return $opt;
1004
}
1005
 
1006
 
1007
#-------------------------------------------------------------------------------
1008
# Function        : ArgsToRef
1009
#
1010
# Description     : Convert an argument list into a hash reference
1011
#                   with error checking
1012
#
1013
# Inputs          : $name                   - User function name
1014
#                   *                       - User arguments
1015
#                                             May be a ref to a hash
1016
#                                             Array of args
1017
#
1018
# Returns         : Ref to a hash
1019
#
1020
sub ArgsToRef
1021
{
1022
    my $name = shift;
1023
    my $uopt;
1024
 
1025
    #
1026
    #   User can pass in:
1027
    #       Nothing at all
1028
    #       A reference to a hash
1029
    #       A hash as a list of argumnts
1030
    #
1031
    if ( $#_ < 0 ) {
1032
 
1033
    } elsif ( UNIVERSAL::isa($_[0],'HASH') ) {
1034
         $uopt = $_[0];
1035
 
1036
    } else {
1037
        #
1038
        #   A list of arguments
1039
        #   Treat it as a hash. Must have an even number of arguments
1040
        #
273 dpurdie 1041
        Error ("$name: Odd number of args to function")
271 dpurdie 1042
            unless ((@_ % 2) == 0);
1043
        $uopt = {@_};
1044
    }
1045
 
1046
    return $uopt;
1047
}
1048
 
1049
################################################################################
1050
#   
1051
#
1052
#   Valid User Arguments
1053
#   Hash value is used to determine if the CopyDir operation must perform
1054
#   extensive matching operations.
1055
#
1056
use constant    Scalar   => 1;
1057
use constant    Match    => 2;
1058
use constant    CodeRef  => 4;
1059
use constant    ArrayRef => 8;
1060
use constant    HashRef  => 16;
1061
 
1062
my %ValidArgs = (
1063
    'DeleteFirst'     => Scalar,
1064
    'DuplicateLinks'  => Scalar,
1065
    'NoSubDirs'       => Scalar | Match,
1066
    'Flatten'         => Scalar,
1067
    'Logger'          => CodeRef,
1068
    'EmptyDirs'       => Scalar,
1069
    'IgnoreDots'      => Scalar | Match,
1070
    'Expert'          => CodeRef,
1071
    'Examine'         => CodeRef,
1072
    'Exists'          => CodeRef,
1073
    'Log'             => Scalar,
1074
    'Error'           => CodeRef,
1075
    'Stats'           => HashRef,
1076
    'Match'           => ArrayRef | Match,
1077
    'MatchRE'         => ArrayRef | Match,
1078
    'MatchDirs'       => ArrayRef | Match,
1079
    'MatchDirsRE'     => ArrayRef | Match,
1080
    'Ignore'          => ArrayRef | Match,
1081
    'IgnoreRE'        => ArrayRef | Match,
1082
    'IgnoreDirs'      => ArrayRef | Match,
1083
    'IgnoreDirsRE'    => ArrayRef | Match,
1084
    'NoOverwrite'     => Scalar,
1085
    'UserData'        => 0,
1086
    'SymlinkFiles'    => Scalar,
1087
    'ReadOnlyFiles'   => Scalar,
273 dpurdie 1088
    'KeepSrcTail'     => Scalar,
1089
    'FileList'        => ArrayRef,
271 dpurdie 1090
);
1091
 
1092
 
1093
#-------------------------------------------------------------------------------
1094
# Function        : ValidateArg
1095
#
1096
# Description     : Validate a user option arguments
1097
#                   Transfer validated options to a target hash
1098
#
1099
# Inputs          : $name                   - User function
1100
#                   $uopt                   - Source option list to process
1101
#                   $topt                   - Target option ref
1102
#
1103
# Returns         : Nothing
1104
#
1105
sub ValidateArg
1106
{
1107
    my ($name, $uopt, $topt ) = @_;
1108
 
1109
    foreach ( keys %{$uopt} )
1110
    {
1111
        #
1112
        #   Option must exist
1113
        #
1114
        Error ("$name. Invalid option: $_")
1115
            unless ( exists $ValidArgs{$_} );
1116
 
1117
        my $ref =  ref($uopt->{$_});
1118
        my $mask = $ValidArgs{$_};
1119
 
1120
        if ( $mask & Scalar )
1121
        {
1122
            Error ("$name. Argument not expecting a ref: $_")
1123
                if ( $ref );
1124
        }
1125
 
1126
        if ( $mask & CodeRef )
1127
        {
1128
            Error ("$name. Argument requires a Code Reference: $_")
1129
                if ( $ref ne 'CODE' );
1130
        }
1131
 
1132
        if ( $mask & ArrayRef )
1133
        {
1134
            Error ("$name. Argument requires an Array Reference: $_")
1135
                if ( $ref ne 'ARRAY' );
1136
        }
1137
 
1138
        if ( $mask & HashRef )
1139
        {
1140
            Error ("$name. Argument requires an Hash Reference: $_")
1141
                if ( $ref ne 'HASH' );
1142
        }
1143
 
1144
        #
1145
        #   If any of the Match options are active, then flag OptMatch
1146
        #   This will be used to speed up searching and processing
1147
        #
1148
        $topt->{'OptMatch'} = 1
1149
            if ( $mask & Match );
1150
 
1151
        #
1152
        #   Insert the user argument
1153
        #
1154
        $topt->{$_} = $uopt->{$_}
1155
    }
1156
}
1157
 
1158
1;
1159