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

Revision 27384, 21.9 KB (checked in by jmt12, 7 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

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 browser.