Subversion Repositories DevTools

Rev

Rev 6133 | Details | Compare with Previous | Last modification | View Log | RSS feed

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