1 # Copyright (C) 1998-2009, Mikhael Goikhman
3 # This program is free software; you can redistribute it and/or modify
4 # it under the terms of the GNU General Public License as published by
5 # the Free Software Foundation; either version 2 of the License, or
6 # (at your option) any later version.
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
13 # You should have received a copy of the GNU General Public License
14 # along with this program; if not, write to the Free Software
15 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17 package General
::FileSystem
;
21 use vars
qw(@ISA @EXPORT);
25 load_file save_file append_file remove_file copy_file move_file
26 make_dir make_path clean_dir remove_dir copy_dir move_dir
27 list_filenames find_file find_executable
28 default_dir_perm preserve_stat parse_path get_cwd
31 use vars
qw($CACHE_FILE_NUM $cache_counter @prev_filenames @prev_file_content_refs);
32 use vars qw($ENABLE_CACHE %NEVER_COPY_FILES %NEVER_REMOVE_FILES);
33 use vars qw($DEFAULT_DIR_PERM $PRESERVED_STAT);
34 use vars qw($DEBUG_ENABLED $ERROR_HANDLER $LOAD_FILE_DIRS $SAVE_FILE_DIR);
37 $ENABLE_CACHE = 0; # this is risky for dynamical files
38 %NEVER_COPY_FILES = ( 'CVS' => 1, 'core' => 1 );
39 %NEVER_REMOVE_FILES = ( 'CVS' => 1 );
40 $DEFAULT_DIR_PERM = 0775;
43 # allow these constants to be set directly from outside
44 $ERROR_HANDLER ||= "warn"; # may be "die", "warn", "quiet" or CODE
46 $LOAD_FILE_DIRS ||= [ "." ]; # for non fully qualified files only
47 $SAVE_FILE_DIR ||= "."; # for non fully qualified files only
51 # ----------------------------------------------------------------------------
55 General::FileSystem - file system specific functions
59 use General::FileSystem "-die", "-debug"; # die on errors
62 make_path("/tmp/my-own/dir");
64 my $file_content_ref = load_file("/etc/issue");
65 save_file("/tmp/my-own/dir/issue", $file_content_ref);
67 # This is equivalent to the previous two lines, but optimized
68 copy_file("/etc/issue", "/tmp/my-own/dir/issue");
70 make_dir("/tmp/my-own/dir2", 0711);
71 copy_file("/etc/issue", "/tmp/my-own/dir2/issue");
72 move_file("/tmp/my-own/dir2/issue", "/tmp/my-own/dir2/issue2");
73 remove_file("/tmp/my-own/dir2/issue2");
74 clean_dir("/tmp/my-own/dir2"); # no effect, it's empty already
76 remove_dir("/tmp/my-own");
79 print "File System Error: $@";
84 use General::FileSystem;
85 copy_file("origin.txt", "backup.txt");
89 This package contains common file operation functions:
91 B<load_file>, B<save_file>, B<append_file>, B<remove_file>, B<copy_file>, B<move_file>,
92 B<make_dir>, B<make_path>, B<clean_dir>, B<remove_dir>, B<copy_dir>, B<move_dir>,
93 B<list_filenames>, B<find_file>, B<find_executable>,
94 B<default_dir_perm>, B<preserve_stat>, B<parse_path>, B<get_cwd>.
96 On fatal file system errors all functions call the error handler, that may
97 throw exception (die), issue a warning or quietly return undef.
98 You may control this by passing one of the arguments I<-die>, I<-warn>
99 or I<-quiet> in B<use> or by setting C<$ERROR_HANDLER> to one of these
100 values (don't specify a dash in this case).
104 L<Cwd>, L<File::Basename>, L<File::Copy>.
109 # ============================================================================
119 while (@_ && $_[0] =~ /^-/) {
121 $ERROR_HANDLER = $1 if /^-(die|warn|quiet)$/i;
122 $DEBUG_ENABLED = $1 if /^-(debug)$/i;
124 $package->export_to_level(1, @_);
129 sub call_error_handler ($) {
131 die "$msg: [$!]\n" if $ERROR_HANDLER eq "die";
132 warn "$msg: [$!]\n" if $ERROR_HANDLER eq "warn";
133 return undef if $ERROR_HANDLER eq "quiet";
134 &$ERROR_HANDLER($msg) if ref($ERROR_HANDLER) eq "CODE";
142 return unless $DEBUG_ENABLED;
143 print STDERR "FileSystem: $msg\n";
147 # ----------------------------------------------------------------------------
155 $content_ref = load_file($filename)
159 Loads file with given file-name from local filesystem.
163 * filename - name of the file to be loaded.
167 Reference to file content string on success, otherwise either dies or warns
168 and returns undef as configured.
173 # ============================================================================
179 @prev_filenames = ("", "", "", "", "", "");
180 @prev_file_content_refs = \("", "", "", "", "", "");
185 my $filename = shift;
187 foreach (@$LOAD_FILE_DIRS) {
188 if (-f "$_/$filename") { $filename = "$_/$filename"; last; }
190 print_log("Loading file $filename") if $DEBUG_ENABLED;
193 for (0 .. $CACHE_FILE_NUM-1) {
194 if ($filename eq $prev_filenames[$_] && -r $filename) {
195 print_log("getting from file cache") if $DEBUG_ENABLED;
196 return $prev_file_content_refs[$_];
201 open(FILE, "<$filename") || return call_error_handler("Can't open $filename");
202 my $content = join("", <FILE>);
203 close(FILE) || return call_error_handler("Can't close $filename");
206 $cache_counter = ($cache_counter+1) % $CACHE_FILE_NUM;
207 $prev_filenames[$cache_counter] = $filename;
208 $prev_file_content_refs[$cache_counter] = \$content;
214 # ----------------------------------------------------------------------------
222 Saves file-content to local filesystem with given file-name.
226 save_file($filename, \$content);
230 * filename - name of the file to be saved into
231 * content_ref - reference to file content string
232 * create_subdirs - optional flag (default is 0 - don't create subdirs)
236 C<1> on success, otherwise either dies or warns and returns undef as configured.
241 # ============================================================================
244 sub save_file ($$;$) {
245 my ($filename, $content_ref, $create_dirs) = @_;
247 if ($filename !~ m=^[/\\]|\w:\\=) {
248 $filename = "$SAVE_FILE_DIR/$filename";
250 print_log("Saving file $filename") if $DEBUG_ENABLED;
251 die("save_file: No SCALAR ref parameter\n")
252 unless ref($content_ref) eq 'SCALAR';
255 for (0 .. $CACHE_FILE_NUM-1) {
256 $prev_file_content_refs[$_] = $content_ref
257 if $filename eq $prev_filenames[$_];
261 my $dirname = dirname($filename);
262 make_path($dirname) unless -d $dirname;
265 open(FILE, ">$filename") || return call_error_handler("Can't open $filename");
266 print FILE $$content_ref;
267 close(FILE) || return call_error_handler("Can't close $filename");
272 # ----------------------------------------------------------------------------
280 Appends file-content to local filesystem with given file-name.
284 append_file($filename, \$appended_content);
288 * filename - name of the file to be saved into
289 * appended_content_ref - reference to appended-content string
293 C<1> on success, otherwise either dies or warns and returns undef as configured.
298 # ============================================================================
301 sub append_file ($$) {
302 my ($filename, $appended_content_ref) = @_;
303 print_log("Append>>file $filename") if $DEBUG_ENABLED;
306 for (0 .. $CACHE_FILE_NUM-1 && -r $filename) {
307 ${$prev_file_content_refs[$_]} .= $$appended_content_ref
308 if $filename eq $prev_filenames[$_];
312 open(FILE, ">>$filename") || return call_error_handler("Can't append to $filename");
313 print FILE $$appended_content_ref;
314 close(FILE) || return call_error_handler("Can't close $filename");
319 # ----------------------------------------------------------------------------
327 Removes all files from given directory.
331 remove_file($filename);
335 * filename - name of the file to be deleted
339 C<1> on success, otherwise either dies or warns and returns undef as configured.
344 # ============================================================================
347 sub remove_file ($;$) {
348 my $filename = shift;
349 print_log("Removin file $filename") if $DEBUG_ENABLED;
350 unlink($filename) || return call_error_handler("Can't unlink $filename");
355 # ----------------------------------------------------------------------------
363 Removes all files from given directory.
367 make_dir($PREVIEW_DIR);
372 * optional creating dir permissions (default is $DEFAULT_DIR_PERM)
376 C<1> on success, otherwise either dies or warns and returns undef as configured.
381 # ============================================================================
386 my $perm = shift || $DEFAULT_DIR_PERM;
387 print_log("Creating dir $dirname, " . sprintf("%o", $perm))
389 mkdir($dirname, $perm) || return call_error_handler("Can't mkdir $dirname");
394 # ----------------------------------------------------------------------------
402 Removes all files from given directory.
406 make_path($PUBLISH_DIR);
411 * optional creating dir permissions (default is $DEFAULT_DIR_PERM)
415 C<1> on success, otherwise either dies or warns and returns undef as configured.
420 # ============================================================================
423 sub make_path ($;$) {
425 my $perm = shift || $DEFAULT_DIR_PERM;
426 print_log("Making path $dirname, " . sprintf("%o", $perm))
429 return 1 if -d $dirname;
430 my $parent_dir = dirname($dirname);
432 local $DEBUG_ENABLED = 0;
433 &make_path($parent_dir, $perm) unless -d $parent_dir;
434 make_dir($dirname, $perm);
440 # ----------------------------------------------------------------------------
448 Copies a file to another location.
452 copy_file($from, $to);
456 * file name to copy from
457 * file name to copy to
461 C<1> on success, otherwise either dies or warns and returns undef as configured.
466 # ============================================================================
470 my ($src_filename, $dst_filename) = @_;
471 print_log("Copying file $src_filename to $dst_filename")
474 # Must manage symbolic links somehow
475 # return if -l $src_filename;
477 copy($src_filename, $dst_filename)
478 or return call_error_handler("Can't copy $src_filename $dst_filename");
480 if ($PRESERVED_STAT) {
481 my ($device, $inode, $mode) = stat($src_filename);
482 chmod($mode, $dst_filename);
488 # ----------------------------------------------------------------------------
496 Moves (or renames) a file to another location.
500 move_file($from, $to);
504 * file name to move from
505 * file name to move to
509 C<1> on success, otherwise either dies or warns and returns undef as configured.
514 # ============================================================================
518 my ($src_filename, $dst_filename) = @_;
519 print_log("Moving file $src_filename to $dst_filename")
522 move($src_filename, $dst_filename)
523 or return call_error_handler("Can't move $src_filename $dst_filename");
528 # ----------------------------------------------------------------------------
536 Removes all files from given directory.
540 clean_dir($PREVIEW_DIR);
546 0 - don't go recursively, unlink files in first level only
547 1 - recursively clean subdirs (default)
549 3 - unlink given directory
553 C<1> on success, otherwise either dies or warns and returns undef as configured.
558 # ============================================================================
561 sub clean_dir ($;$) {
563 my $recursive = shift || 1;
564 die("clean_dir: Unsupported flag $recursive\n")
565 if $recursive > 3 || $recursive < 0;
566 print_log(($recursive != 3 ? "Cleaning" : "Removing") . " dir $dirname "
567 . ["files only", "recursively files only", "recursively", "completely"]->[$recursive])
570 local $DEBUG_ENABLED = 0;
573 my $filenames = list_filenames($dirname);
576 foreach (@$filenames) {
577 next if $NEVER_REMOVE_FILES{$_};
578 my $filename = "$dirname/$_";
579 if (-d $filename) { push @subdirs, $filename; }
580 else { unlink("$filename") || return call_error_handler("Can't unlink $filename"); }
585 clean_dir($_, $recursive);
586 rmdir($_) || return call_error_handler("Can't unlink $_") if $recursive == 2;
587 } @subdirs if $recursive;
588 rmdir($dirname) || return call_error_handler("Can't unlink $dirname") if $recursive == 3;
594 # ----------------------------------------------------------------------------
602 Entirely removes given directory and its content (if any).
603 This is an alias to C<clean_dir(3)>.
607 remove_dir($TMP_DIR);
615 C<1> on success, otherwise either dies or warns and returns undef as configured.
620 # ============================================================================
625 return clean_dir($dirname, 3);
629 # ----------------------------------------------------------------------------
637 Recursively copies all files and subdirectories inside given directory
640 Destination directory must not exist. Use: C<trap { remove_dir($dest); };>
641 to remove it before copying.
645 copy_dir($dir_from, $dir_to);
649 * source directory to copy
650 * destination directory to copy to (may not exist)
651 * optional creating dir permissions (default is $DEFAULT_DIR_PERM)
655 C<1> on success, otherwise either dies or warns and returns undef as configured.
660 # ============================================================================
664 my ($src_dirname, $dst_dirname, $perm) = @_;
666 return call_error_handler("Directory $src_dirname does not exist")
667 unless -d $src_dirname;
668 make_dir($dst_dirname, $perm) unless -d $dst_dirname;
670 print_log("Copying dir $src_dirname to $dst_dirname recursively")
673 local $DEBUG_ENABLED = 0;
677 my $filenames = list_filenames($src_dirname);
680 foreach (@$filenames) {
681 next if $NEVER_COPY_FILES{$_};
682 my $src_filename = "$src_dirname/$_";
683 my $dst_filename = "$dst_dirname/$_";
684 if (-d $src_filename) { push @subdirs, $_; }
685 elsif (-l $src_filename) { next if "# We ignore links for now! TO FIX!" }
686 else { copy_file($src_filename, $dst_filename) or $error = 1; }
691 my $src_subdirname = "$src_dirname/$_";
692 my $dst_subdirname = "$dst_dirname/$_";
693 ©_dir($src_subdirname, $dst_subdirname) or $error = 1;
696 return call_error_handler("Errors while copying some files/subdirs in $src_dirname to $dst_dirname")
702 # ----------------------------------------------------------------------------
710 Moves (or actually renames) a directory to another location.
712 Destination directory must not exist. Use: C<trap { remove_dir($dest); };>
713 to remove it before copying.
717 move_dir($dir_from, $dir_to);
721 * source directory to move from
722 * destination directory to move to (must not exist)
726 C<1> on success, otherwise either dies or warns and returns undef as configured.
731 # ============================================================================
735 my ($src_dirname, $dst_dirname) = @_;
736 print_log("Moving dir $src_dirname to $dst_dirname")
739 rename($src_dirname, $dst_dirname)
740 or return call_error_handler("Can't rename $src_dirname $dst_dirname");
745 # ----------------------------------------------------------------------------
747 =head2 list_filenames
753 Returns the file names in the given directory including all types of files
754 (regular, directory, link, other), not including '.' and '..' entries.
760 foreach my $file (@{list_filenames($dir)}) {
761 print "File $file\n" if -f "$dir/$file";
762 print "Dir $file\n" if -d "$dir/$file";
767 * directory to list (or array ref of directories)
768 * optional flag, 1 means work recursively, the default is 0
772 Array ref of scalars (file names) on success.
773 Otherwise either dies or warns and returns undef as configured.
778 # ============================================================================
781 sub list_filenames ($;$) {
783 my $recursive = shift || 0;
784 if (ref($dirname) eq "ARRAY") {
786 foreach (@$dirname) { push @files, &list_filenames($_); }
789 print_log("Listing dir $dirname") if $DEBUG_ENABLED;
791 opendir(DIR, $dirname) || return call_error_handler("Can't opendir $dirname");
792 my @files = grep { $_ ne '.' && $_ ne '..' } readdir(DIR);
793 closedir(DIR) || return call_error_handler("Can't closedir $dirname");
797 for (; $i < @files; ) {
798 my $subdir = $files[$i];
799 if (-d "$dirname/$subdir") {
800 splice(@files, $i, 1, map { "$subdir/$_" }
801 @{&list_filenames("$dirname/$subdir")});
812 # ----------------------------------------------------------------------------
820 Searches for the given file in the given directories.
822 Returns the fully qualified file name.
826 my $gtkrc = find_file(".gtkrc", [$home, "$home/.gnome"]);
830 * file name to search for
831 * array ref of directories to search in
835 File name with full path if found, or undef if not found.
840 # ============================================================================
844 my $filename = shift;
846 die "find_file: no dirs given\n" unless ref($dirs) eq "ARRAY";
848 my $file_path = "$_/$filename";
849 return $file_path if -f $file_path;
855 # ----------------------------------------------------------------------------
857 =head2 find_executable
863 Searches for the given executable file in the directories that are in the
864 environmebt variable $PATH or in the additional parameter.
866 Returns the fully qualified file name.
870 my $gzip_exe = find_executable("gzip", ["/usr/gnu/bin", "/gnu/bin"]);
874 * file name to search for (only executables are tested)
875 * optional array ref of extra directories to search in
879 File name with full path if found, or undef if not found.
884 # ============================================================================
887 sub find_executable ($;$) {
888 my $filename = shift;
889 my $extra_dirs = shift;
890 my @dirs = split(":", $ENV{"PATH"} || "");
891 if (ref($extra_dirs) eq "ARRAY") {
892 push @dirs, @$extra_dirs;
895 my $file_path = "$_/$filename";
896 return $file_path if -x $file_path;
902 # ----------------------------------------------------------------------------
904 =head2 default_dir_perm
910 This functions changes default directory permissions, used in
911 C<make_dir>, C<make_path>, C<copy_dir> and C<move_dir> functions.
913 The default of this package is 0775.
915 If no parameters specified, the current value is returned.
919 default_dir_perm(0700);
923 * optional default directory permission (integer)
932 # ============================================================================
935 sub default_dir_perm (;$) {
936 return if $^O =~ /Win|DOS/;
937 my $new_value = shift;
938 my $old_value = $DEFAULT_DIR_PERM;
940 if (defined $new_value) {
941 print_log("default_dir_perm = $new_value") if $DEBUG_ENABLED;
942 $DEFAULT_DIR_PERM = $new_value;
948 # ----------------------------------------------------------------------------
956 This functions changes behavior of C<copy_file> and C<copy_dir> functions.
957 If 0 is given as a parameter stats will not be preserved.
959 TODO: specify values for diferent preserves:
962 1 mode file mode (type and permissions)
963 2 uid numeric user ID of file's owner
964 4 gid numeric group ID of file's owner
965 8 atime last access time since the epoch
966 16 mtime last modify time since the epoch
967 32 ctime inode change time (NOT creation time!) since the epo
969 The default of this package is 0.
971 If no parameters specified, nothing is set (only current value is returned).
979 * optional flag (currently 0 or 1)
988 # ============================================================================
991 sub preserve_stat (;$) {
992 return if $^O =~ /Win|DOS/;
993 my $new_value = shift;
994 my $old_value = $PRESERVED_STAT;
996 if (defined $new_value) {
997 print_log("preserve_stat = $new_value") if $DEBUG_ENABLED;
998 $PRESERVED_STAT = $new_value;
1004 # ----------------------------------------------------------------------------
1012 my ($dirname, $basename) = parse_path($filename);
1016 # in: "/data/projects/magazine" out: ("/data/projects", "magazine")
1017 # in: "/magazine" out: ("", "magazine")
1018 # in: "dir/" out: (dir", "")
1019 # in: "magazine" out: (".", "magazine")
1021 # in: "c:\projects\magazine" out: ("c:\projects", "magazine")
1022 # in: "c:\magazine" out: ("c:", "magazine")
1023 # in: "c:magazine" out: ("c:.", "magazine")
1027 Returns a list of 2 scalars: directory name and base name. All unix and dos
1028 file names supported.
1030 Note, the rule is this: you can join both scalars using a directory delimiter
1031 (slash or backslash) and you will always get the the original (logical)
1037 # ============================================================================
1040 sub parse_path ($) {
1042 if ($path =~ m=^(.*)[/\\]+([^/\\]*)$=) {
1045 # support even funny dos form c:file
1046 return $path =~ m=^(\w:)(.*)$=
1053 # ----------------------------------------------------------------------------
1061 my $cwd = get_cwd();
1065 Returns the current working directory.
1070 # ============================================================================
1074 $^O eq "MSWin32" ? Win32::GetCwd() : require "getcwd.pl" && getcwd();
1078 # ----------------------------------------------------------------------------
1082 All global functions and constants in this package should probably be
1083 instantiated into a class object. As usual there are pros and cons.
1087 Mikhael Goikhman <migo@homemail.com>
1090 # ============================================================================