Subversion Repositories DevTools

Rev

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

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