source: gs2-extensions/parallel-building/trunk/src/perllib/FileUtils.pm@ 27384

Last change on this file since 27384 was 27384, checked in by jmt12, 11 years ago

An overriding version of FileUtils that provides several drivers required for parallel processing. In the future we may want to split this off as its own extension and add further drivers for HTTP, FTP, WebDAV etc

File size: 21.9 KB
Line 
1###########################################################################
2#
3# FileUtils.pm -- functions for dealing with files. Will delegate to the
4# appropriate filesystem driver based upon any file
5# protocol specified and dependent on configuration as
6# defined by the collection admin
7#
8# A component of the Greenstone digital library software
9# from the New Zealand Digital Library Project at the
10# University of Waikato, New Zealand.
11#
12# Copyright (C) 2013 New Zealand Digital Library Project
13#
14# This program is free software; you can redistribute it and/or modify
15# it under the terms of the GNU General Public License as published by
16# the Free Software Foundation; either version 2 of the License, or
17# (at your option) any later version.
18#
19# This program is distributed in the hope that it will be useful,
20# but WITHOUT ANY WARRANTY; without even the implied warranty of
21# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22# GNU General Public License for more details.
23#
24# You should have received a copy of the GNU General Public License
25# along with this program; if not, write to the Free Software
26# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
27#
28###########################################################################
29
30package FileUtils;
31
32# Perl Modules
33use strict;
34use Symbol qw<qualify>;
35
36# Greenstone Modules
37use util;
38
39# Configuration
40my $debug = 0;
41
42# /** @function _callFunction($driver_name, $function_name, ...)
43# * Make a function call to a dynamically loaded database driver.
44# * @param $driver_name - The name of the file protocol driver to load
45# * @param $function_name - The function within the driver to call
46# * @param <rest> - The parameters to be passed to the function called
47# */
48sub _callFunction
49{
50 my $driver_name = shift(@_);
51 my $function_name = shift(@_);
52 &_prettyPrint(0, $driver_name, $function_name, @_) unless (!$debug);
53 # Need to look within fileutils directory
54 my $package_name = 'FileUtils::' . $driver_name;
55 # Try to load the requested infodb type
56 if (!&_loadDriver($package_name))
57 {
58 &printError('Failed to load requested file protocol driver: ' . $package_name, 1);
59 }
60 # Then make call to the newly created package
61 no strict;
62 # Better check that the function we are about to call exists
63 my $symbol = qualify($function_name, $package_name);
64 unless ( defined &{$symbol} )
65 {
66 &printError('Function not found: ' . $package_name . '::' . $function_name, 1);
67 }
68 # Call the function and get result if applicable
69 my $result = &{$symbol}(@_);
70 &_prettyPrint(1, $result) unless (!$debug);
71 return $result;
72}
73# /** callFunction() **/
74
75# /** @function _prettyPrint($type, ...)
76# * Print a debugging message to STDERR constructed from the <rest> based
77# * upon the type.
78# * @param type - If 0, output the start of a function with a listing of its
79# * parameters. If 1, output the result of a function. 2 is
80# * used for function errors. Default to simply printing what-
81# * ever else is in <rest>
82# */
83sub _prettyPrint
84{
85 my $type = shift(@_);
86 if (!defined $type || $type > 2)
87 {
88 $type = -1;
89 }
90 my ($package, $filename, $line, $function) = caller(1);
91 my $message;
92 # Start of a function
93 if (0 == $type)
94 {
95 $message = $package . '::' . $function . '(';
96 my $argument = shift(@_);
97 my $first = 1;
98 while (defined $argument)
99 {
100 if (!$first)
101 {
102 $message .= ', ';
103 }
104 else
105 {
106 $first = 0;
107 }
108 if ($argument =~ /\D/)
109 {
110 $message .= '"' . $argument . '"';
111 }
112 else
113 {
114 $message .= $argument;
115 }
116 $argument = shift(@_);
117 }
118 $message .= ')';
119 }
120 # Result of a function
121 elsif (1 == $type)
122 {
123 $message = $package . '::' . $function . '() => ';
124 my $result = shift(@_);
125 if ($result =~ /\D/)
126 {
127 $message .= '"' . $result . '"';
128 }
129 else
130 {
131 $message .= $result;
132 }
133 }
134 elsif (2 == $type)
135 {
136 my $error = shift(@_);
137 $message = 'Error in ' . $package . '::' . $function . '()! ' . $error;
138 }
139 # Else we leave the message as it is
140 else
141 {
142 $message = join("\n", @_);
143 }
144 print STDERR "[" . time() . "] " . $message . "\n";
145}
146# /** _prettyPrint($type, $function, $message) **/
147
148# /** @function _determineDriver()
149# * Given a file path determine the appropriate protocol. For now anything
150# * other than a full path beginning with an explicit protocol will default
151# * to using 'local' file functions.
152# * @return 'local'
153# */
154sub _determineDriver
155{
156 my $path = shift(@_);
157 &_prettyPrint(0, $path) unless (!$debug);
158 # Determine the appropriate driver from the protocol
159 my $driver = 'LocalFS';
160 # - this is were I'll eventually have the ability to configure
161 # what driver handles what protocol, hopefully from the collect.cfg
162 my $colon_index = index($path, ':');
163 if ($colon_index > -1)
164 {
165 my $protocol = substr($path, 0, $colon_index);
166 # check the perl module exists
167 eval
168 {
169 require 'FileUtils/' . $protocol . '.pm';
170 };
171 if ($@)
172 {
173 die($@);
174 print STDERR 'Warning! FileUtils::_determineDriver() driver not found (defaulting to local filesystem):' . $protocol . "\n" . $@ . "\n";
175 }
176 else
177 {
178 $driver = $protocol;
179 }
180 }
181 &_prettyPrint(1, $driver) unless (!$debug);
182 return $driver;
183}
184# /** _determineDriver()
185
186# /** @function _loadDriver($class, ...)
187# * Runtime class loading for use in FileUtils to load various protocol
188# * drivers, possibly configured in the collect.cfg, at runtime.
189# * @param $class - The class name (including any path) to load
190# * @param <rest> - any function aliases you want exported
191# */
192sub _loadDriver
193{
194 my $class = shift(@_);
195 &_prettyPrint(0, $class) unless (!$debug);
196 # Convert the Perl Module-like name into a file path
197 (my $file = "$class.pm") =~ s|::|/|g;
198 # - ensure we haven't already loaded this class
199 unless( $INC{$file} )
200 {
201 require $file;
202 }
203 # - this is the magic that actually instantiates the class (rubberstamp?)
204 # - we pass @_ to action any function aliases exports requested
205 eval
206 {
207 $class->import(@_);
208 };
209 # - by now the driver file should have been loaded
210 my $result = defined $INC{$file};
211 &_prettyPrint(1, $result) unless (!$debug);
212 return $result;
213}
214# /** _loadDriver($class, ...) **/
215
216################################################################################
217
218
219## @function printError()
220#
221sub printError
222{
223 my ($message, $fatal) = @_;
224 my ($package, $filename, $line, $function) = caller(1);
225 if (defined $!)
226 {
227 $message .= ' (' . $! . ')';
228 }
229 if (defined $fatal && $fatal)
230 {
231 die('Fatal Error! ' . $package . '::' . $function . '() - ' . $message ."\n");
232 }
233 else
234 {
235 print STDERR 'Error! ' . $package . '::' . $function . '() - ' . $message ."\n";
236 }
237}
238## printError()
239
240
241## @function printWarning
242#
243sub printWarning
244{
245 my ($message) = @_;
246 my ($package, $filename, $line, $function) = caller(1);
247 print STDERR 'Warning! ' . $package . '::' . $function . '() - ' . $message . "\n";
248}
249## printWarning()
250
251################################################################################
252######################## Legacy function name mappings ########################
253################################################################################
254# Note: there are lots of functions involving files/directories/paths etc found
255# in utils.pm that are not represented here. My intention was to just have those
256# functions that need to be dynamic based on filesystem, or need some rejigging
257# to be filesystem aware. This is an argument, I guess, for moving some of the
258# other functions here so that they are nicely encapsulated - but the question
259# is what to do with functions like filename_within_directory_url_format() which
260# is more URL based than file based... dunno.
261################################################################################
262
263sub cachedir {return synchronizeDirectory(@_);}
264sub cp {return copyFiles(@_);}
265sub cp_r {print "implement cp_r()";}
266sub cp_r_nosvn {print "implement cp_r_nosvn()";}
267sub cp_r_toplevel {print "implement cp_r_toplevel()";}
268sub differentfiles {return &differentFiles(@_);}
269sub dir_exists {return &directoryExists(@_);}
270sub file_exists {return &fileExists(@_);}
271sub file_lastmodified {return &modificationTime(@_);}
272sub file_readdir {return readDirectory(@_);}
273sub file_size {return &fileSize(@_);}
274sub filename_cat {return filenameConcatenate(@_);}
275sub filename_is_absolute {return &isFilenameAbsolute();};
276sub filtered_rm_r {print "implement filtered_rm_r()";}
277sub hard_link {print "implement hard_link()";}
278sub is_dir_empty {return &isDirectoryEmpty();}
279sub mk_all_dir {return &makeAllDirectories(@_);}
280sub mk_dir {return &makeDirectory(@_);}
281sub mv {return &moveFiles(@_);}
282sub mv_dir_contents {print "implement mv_dir_contents()";}
283sub rm {print "implement rm()";}
284sub rm_debug {print "implement rm_debug()";}
285sub rm_r {print "implement rm_r()";}
286sub soft_link {print "implement soft_link()";}
287
288################################################################################
289########## Common functions ##########
290################################################################################
291# Note: these are the file-based functions that are not dynamic in themselves,
292# but that need significant changes to support multiple possible filesystems.
293################################################################################
294
295
296## @function differentFiles
297# (previous util.pm version used -e, -d, and 'stat', none of which support
298# * filesystems such as hadoop)
299# */
300sub differentFiles
301{
302 my ($file1, $file2, $verbosity) = @_;
303 if (!defined $verbosity)
304 {
305 $verbosity = 1
306 }
307
308 # remove trailing slashes
309 $file1 =~ s/\/+$//;
310 $file2 =~ s/\/+$//;
311
312 # chop off the last part of the path as the file/dir name
313 my ($file1name) = $file1 =~ /\/([^\/]*)$/;
314 my ($file2name) = $file2 =~ /\/([^\/]*)$/;
315
316 # - cheapest first; test the two filename strings are the same
317 if ($file1name ne $file2name)
318 {
319 print STDERR "filenames are not the same\n" if ($verbosity >= 2);
320 return 1;
321 }
322
323 if (!&pathExists($file1) || !&pathExists($file2))
324 {
325 print STDERR "one or other file doesn't exist\n" if ($verbosity >= 2);
326 return -1;
327 }
328
329 if (&directoryExists($file1))
330 {
331 if (!&directoryExists($file2))
332 {
333 print STDERR "one file is a directory\n" if ($verbosity >= 2);
334 return 1;
335 }
336 return 0;
337 }
338
339 # both must be regular files
340 unless (&fileExists($file1) && &fileExists($file2))
341 {
342 print STDERR "one file is not a regular file\n" if ($verbosity >= 2);
343 return 1;
344 }
345
346 # the size of the files must be the same
347 if (&fileSize($file1) != &fileSize($file2))
348 {
349 print STDERR "different sized files\n" if ($verbosity >= 2);
350 return 1;
351 }
352
353 # the second file cannot be older than the first
354 if (&modificationTime($file1) > &modificationTime($file2))
355 {
356 print STDERR "file is older\n" if ($verbosity >= 2);
357 return 1;
358 }
359
360 return 0;
361}
362# /** differentFiles() **/
363
364
365## @function fileGetContents()
366#
367sub fileGetContents
368{
369 my ($path) = @_;
370 my $content;
371 my $driver = &FileUtils::_determineDriver($path);
372 my $filesize = &FileUtils::_callFunction($driver, 'fileSize', $path);
373 my $fh;
374 &FileUtils::_callFunction($driver, 'openFileHandle', $path, '<', \$fh);
375 sysread($fh, $content, $filesize);
376 &FileUtils::_callFunction($driver, 'closeFileHandle', \$fh);
377 return $content;
378}
379## fileGetContents()
380
381
382## @function filePutContents()
383#
384sub filePutContents
385{
386 my ($path, $str) = @_;
387 my $driver = &FileUtils::_determineDriver($path);
388 my $fh;
389 &FileUtils::_callFunction($driver, 'openFileHandle', $path, '>', \$fh);
390 print $fh $str;
391 &FileUtils::_callFunction($driver, 'closeFileHandle', \$fh);
392 return 1;
393}
394## filePutContents(path, str)
395
396
397## @function sanitizePath()
398#
399sub sanitizePath
400{
401 my ($path) = @_;
402 # fortunately filename concatenate will perform all the double slash removal,
403 # end slash removal we need, and in a protocol aware fashion
404 return &filenameConcatenate($path);
405}
406## sanitizePath()
407
408
409################################################################################
410
411
412## @function canRead()
413#
414sub canRead
415{
416 my ($path) = @_;
417 my $driver = &FileUtils::_determineDriver($path);
418 return &FileUtils::_callFunction($driver, 'fileTest', $path, '-R');
419}
420## canRead()
421
422
423## @function closeFileHandle()
424#
425sub closeFileHandle
426{
427 my $path = shift(@_);
428 my $driver = &FileUtils::_determineDriver($path);
429 return &FileUtils::_callFunction($driver, 'closeFileHandle', @_);
430}
431## closeFileHandle()
432
433# /**
434# */
435sub copyFiles
436{
437 return &transferFiles(@_, 'COPY');
438}
439# /** copyFiles() **/
440
441# /**
442# */
443sub directoryExists
444{
445 my $path = shift(@_);
446 my $driver = &FileUtils::_determineDriver($path);
447 return &FileUtils::_callFunction($driver, 'fileTest', $path, '-d');
448}
449# /** directoryExists($path) **/
450
451# /** @function file_exists($path)
452# * Determine if the given file path exists on the target filesystem
453# * @param path - the path to the file to test
454# * @return true if the file exists, false otherwise
455sub fileExists
456{
457 my $path = shift(@_);
458 my $driver = &FileUtils::_determineDriver($path);
459 my $result = &FileUtils::_callFunction($driver, 'fileTest', $path, '-f');
460 return $result;
461}
462# /** fileExists(path) **/
463
464# /** @function filenameConcatenate(<rest>)
465# */
466sub filenameConcatenate
467{
468 my $first_path_part = shift(@_);
469 my $path = '';
470 if (defined $first_path_part)
471 {
472 my $driver = &FileUtils::_determineDriver($first_path_part);
473 $path = &FileUtils::_callFunction($driver, 'filenameConcatenate', $first_path_part, @_);
474 }
475 return $path;
476}
477# /** filenameConcatenate(<rest>) **/
478
479# /**
480# */
481sub fileSize
482{
483 my $path = shift(@_);
484 my $driver = &_determineDriver($path);
485 return &_callFunction($driver, 'fileSize', $path);
486}
487# /** fileSize() **/
488
489# /**
490# */
491sub hardLink
492{
493 my $src_file = shift(@_);
494 my $dst_file = shift(@_);
495 my $src_driver = &FileUtils::_determineDriver($src_file);
496 my $dst_driver = &FileUtils::_determineDriver($dst_file);
497 # you can only symbolic link within the same file system - always
498 if ($src_driver eq 'LocalFS' && $src_driver eq $dst_driver)
499 {
500 &FileUtils::_callFunction($src_driver, 'linkFile', 'HARD', $src_file, $dst_file);
501 }
502 # substitute a copy
503 elsif ($src_driver ne 'LocalFS')
504 {
505 &printWarning('Cannot symbolic link on non-local file systems - copying instead: ' . $src_file . ' => ' . $dst_file);
506 &transferFiles($src_file, $dst_file, 'COPY');
507 }
508 else
509 {
510 &printWarning('Cannot symbolic link between file systems - copying instead: ' . $src_file . ' => ' . $dst_file);
511 &transferFiles($src_file, $dst_file, 'COPY');
512 }
513}
514# /** hardLink() **/
515
516## @function isFilenameAbolsute()
517#
518# Determine if the given path is an absolute path (as compared to relative)
519#
520sub isFilenameAbsolute
521{
522 my $path = shift(@_);
523 my $driver = &FileUtils::_determineDriver($path);
524 return &FileUtils::_callFunction($driver, 'isFilenameAbsolute', $path, '-l');
525}
526## isFilenameAbsolute()
527
528## @function isSymbolicLink()
529#
530# Determine if the given path is a symbolic link
531#
532sub isSymbolicLink
533{
534 my $path = shift(@_);
535 my $driver = &FileUtils::_determineDriver($path);
536 return &FileUtils::_callFunction($driver, 'fileTest', $path, '-l');
537}
538## isSymbolicLink()
539
540# /** @function makeAllDirectories
541# */
542sub makeAllDirectories
543{
544 my $path = shift(@_);
545 my $driver = &FileUtils::_determineDriver($path);
546 return &FileUtils::_callFunction($driver, 'makeAllDirectories', $path);
547}
548# /** makeAllDirectories() **/
549
550# /**
551# */
552sub makeDirectory
553{
554 my $path = shift(@_);
555 my $driver = &FileUtils::_determineDriver($path);
556 # check if the directory already exists - in which case our job is done :)
557 my $result = &FileUtils::_callFunction($driver, 'fileTest', $path, '-d');
558 # not yet - better try and create it then
559 if (!$result)
560 {
561 $result = &FileUtils::_callFunction($driver, 'makeDirectory', $path);
562 }
563 return $result;
564}
565# /** makeDirectory(path) **/
566
567# /**
568# */
569sub modificationTime
570{
571 my $path = shift(@_);
572 my $driver = &_determineDriver($path);
573 return &_callFunction($driver, 'modificationTime', $path);
574}
575# /** modificationTime() **/
576
577# /**
578# */
579sub moveFiles
580{
581 return &transferFiles(@_, 'MOVE');
582}
583# /** moveFiles() **/
584
585# /**
586# */
587sub openFileHandle
588{
589 my $path = shift(@_);
590 # I'll set mode to read by default, as that is less destructive to precious
591 # files on your system...
592 my $mode = shift(@_) || '<';
593 # the all important determining of the driver by protocol
594 my $driver = &FileUtils::_determineDriver($path);
595 # the function call will return true on success, with the reference to the
596 # file handle hopefully populated with a lovely file descriptor
597 return &FileUtils::_callFunction($driver, 'openFileHandle', $path, $mode, @_);
598}
599# /** openFileHandle($file_handle_ref, $path, $mode) **/
600
601## @function pathExists()
602#
603# Determine if a certain path exists on the file system - regardless of whether
604# it is a file or a directory (the equivalent of -e)
605#
606sub pathExists
607{
608 my $path = shift(@_);
609 my $driver = &FileUtils::_determineDriver($path);
610 return &FileUtils::_callFunction($driver, 'fileTest', $path, '-e');
611}
612## pathExists()
613
614
615## @function readDirectory()
616#
617# Provide a function to return the files within a directory that is aware
618# of protocols other than file://
619# @param $dirname the full path to the directory
620#
621sub readDirectory
622{
623 my $path = shift(@_);
624 my $driver = &FileUtils::_determineDriver($path);
625 return &FileUtils::_callFunction($driver, 'readDirectory', $path);
626}
627## readDirectory()
628
629
630## @function removeFiles()
631#
632# Removes files (but not directories)
633# @param files - An array of filepaths to remove
634#
635sub removeFiles
636{
637 my (@files) = @_;
638 my $num_removed = 0;
639 # Remove the files
640 foreach my $path (@files)
641 {
642 my $driver = &FileUtils::_determineDriver($path);
643 if (&FileUtils::_callFunction($driver, 'removeFiles', $path))
644 {
645 $num_removed++;
646 }
647 }
648 # Check to make sure all of them were removed
649 if ($num_removed != scalar(@files))
650 {
651 &printError('Not all files were removed');
652 $num_removed = 0;
653 }
654 return $num_removed;
655}
656## removeFile(files)
657
658
659## @function removeFilesFiltered()
660#
661sub removeFilesFiltered
662{
663 my $maybe_paths = shift(@_);
664 my @paths = (ref $maybe_paths eq "ARRAY") ? @$maybe_paths : ($maybe_paths);
665 my $num_removed = 0;
666 foreach my $path (@paths)
667 {
668 my $driver = &FileUtils::_determineDriver($path);
669 $num_removed += &FileUtils::_callFunction($driver, 'removeFilesFiltered', $path, @_);
670 }
671 return $num_removed;
672}
673## removeFilesFiltered()
674
675
676## @function removeFilesRecursive()
677#
678# The equivalent of "rm -rf" with all the dangers therein
679#
680sub removeFilesRecursive
681{
682 my $maybe_paths = shift(@_);
683 my @paths = (ref $maybe_paths eq "ARRAY") ? @$maybe_paths : ($maybe_paths);
684 my $num_removed = 0;
685 foreach my $path (@paths)
686 {
687 my $driver = &FileUtils::_determineDriver($path);
688 $num_removed += &FileUtils::_callFunction($driver, 'removeFilesRecursive', $path);
689 }
690 return $num_removed;
691}
692## removeFilesRecursive()
693
694
695## @function softLink()
696#
697sub softLink
698{
699 my $src_file = shift(@_);
700 my $dst_file = shift(@_);
701 my $src_driver = &FileUtils::_determineDriver($src_file);
702 my $dst_driver = &FileUtils::_determineDriver($dst_file);
703 # you can only symbolic link within the same file system - always
704 if ($src_driver eq 'LocalFS' && $src_driver eq $dst_driver)
705 {
706 &FileUtils::_callFunction($src_driver, 'linkFile', 'SOFT', $src_file, $dst_file, @_);
707 }
708 # substitute a copy
709 elsif ($src_driver ne 'LocalFS')
710 {
711 &printWarning('Cannot symbolic link on non-local file systems - copying instead: ' . $src_file . ' => ' . $dst_file);
712 &transferFiles($src_file, $dst_file, 'COPY');
713 }
714 else
715 {
716 &printWarning('Cannot symbolic link between file systems - copying instead: ' . $src_file . ' => ' . $dst_file);
717 &transferFiles($src_file, $dst_file, 'COPY');
718 }
719}
720## softLink()
721
722
723## @function supportsSymbolicLink()
724#
725sub supportsSymbolicLink
726{
727 my $path = shift(@_);
728 my $driver = &FileUtils::_determineDriver($path);
729 &FileUtils::_callFunction($driver, 'supportsSymbolicLink');
730}
731## supportsSymbolicLink()
732
733
734## @function synchronizeDirectory()
735#
736sub synchronizeDirectory
737{
738 my $fromdir = shift(@_);
739 my $driver = &FileUtils::_determineDriver($fromdir);
740 &FileUtils::_callFunction($driver, 'synchronizeDirectory', $fromdir, @_);
741}
742## synchronizeDirectory()
743
744
745## @function transferFiles()
746# @param paths - one or more source paths
747# @param dst_path - the destination path
748# @param mode - copy or move
749#
750sub transferFiles
751{
752 my $transfer_mode = pop(@_);
753 my $dst_path = pop(@_);
754 my (@src_paths) = @_;
755 &_prettyPrint(0, @src_paths, $dst_path, $transfer_mode) unless (!$debug);
756 my $result = 0;
757 my $dst_driver = &_determineDriver($dst_path);
758 if (scalar (@src_paths) == 0)
759 {
760 &printError('No destination directory given');
761 }
762 elsif ((scalar (@src_paths) > 1) && (!&directoryExists($dst_path)))
763 {
764 &printError('If multiple source files are given the destination must be a directory');
765 }
766 else
767 {
768 foreach my $src_path (@src_paths)
769 {
770 my $src_driver = &_determineDriver($src_path);
771 if ($src_driver eq 'LocalFS')
772 {
773 # Local to local
774 if ($dst_driver eq 'LocalFS')
775 {
776 $result += &_callFunction($src_driver, 'transferFile', $transfer_mode, $src_path, $dst_path);
777 }
778 # Local to X
779 else
780 {
781 $result += &_callFunction($dst_driver, 'transferFileFromLocal', $transfer_mode, $src_path, $dst_path);
782 }
783 }
784 # X to Local
785 elsif ($dst_driver eq 'LocalFS')
786 {
787 $result += &_callFunction($src_driver, 'transferFileToLocal', $transfer_mode, $src_path, $dst_path);
788 }
789 # X to X
790 elsif ($src_driver eq $dst_driver)
791 {
792 $result += &_callFunction($src_driver, 'transferFile', $transfer_mode, $src_path, $dst_path);
793 }
794 # X to Y... not supported
795 else
796 {
797 &printError('transfer between two non-local file systems not supported');
798 }
799 }
800 $result = (scalar(@src_paths) == $result);
801 }
802 return $result;
803}
804## transferFiles()
805
8061;
Note: See TracBrowser for help on using the repository browser.