commit 3d69b3355b084a09aaa2a64a4b7d47e9296d4b01
parent 9c5afc6b5d09f269a33c30bb8567157e1967533a
Author: lumidify <nobody@lumidify.org>
Date: Mon, 23 Mar 2020 14:24:40 +0100
More fixes; add some documentation
Diffstat:
9 files changed, 100 insertions(+), 22 deletions(-)
diff --git a/lumia.pl b/lumia.pl
@@ -28,18 +28,32 @@ use File::Basename qw(basename dirname);
use File::Copy qw(move copy);
use File::Path qw(remove_tree make_path);
use String::ShellQuote;
-use Cwd qw(getcwd);
+use Cwd qw(realpath);
use POSIX qw(SIGINT);
use Data::Dumper;
use Scalar::Util qw(looks_like_number);
use Getopt::Long;
+my $CKSUM_CMD = 'cksum -q';
+my %SPECIAL_FILES = (
+ ".lumidify_archive_cksums" => 1,
+ ".lumidify_archive_cksums.cksum" => 1,
+ ".lumidify_archive_ignore" => 1,
+ ".lumidify_archive_dirs" => 1
+);
+
+# escape a filename for writing into the checksum files
sub escape_filename {
my $file = shift;
$file =~ s/\\/\\\\/g;
$file =~ s/"/\\"/g;
return $file;
}
+
+# make a generic file iterator
+# $file_func determines whether a file should be returned by the iterator
+# $dir_func is called for each directory and returns all files that
+# should be added to the queue
sub make_file_iter {
my ($file_func, $dir_func, @queue) = @_;
return sub {
@@ -56,6 +70,8 @@ sub make_file_iter {
};
}
+# make a basic filename iterator, which simply returns all files
+# for which $file_func returns a true value
sub make_file_iter_basic {
my ($file_func, @files) = @_;
make_file_iter $file_func, sub {
@@ -70,10 +86,15 @@ sub make_file_iter_basic {
}, @files;
}
+# make a basic directory iterator, which only returns all directories
sub make_dir_iter {
make_file_iter_basic sub {-d $_[0]}, @_;
}
+# make an interator that only returns the directories which are present
+# in the .lumidify_archive_dirs files
+# note: this returns nonexistent directories if those are still
+# specified in the lumia files
sub make_lumia_iter {
make_file_iter sub {1}, sub {
my $path = "$_[0]/.lumidify_archive_dirs";
@@ -93,14 +114,7 @@ sub make_lumia_iter {
}, @_;
}
-my $CKSUM_CMD = 'cksum -q';
-my %SPECIAL_FILES = (
- ".lumidify_archive_cksums" => 1,
- ".lumidify_archive_cksums.cksum" => 1,
- ".lumidify_archive_ignore" => 1,
- ".lumidify_archive_dirs" => 1
-);
-
+# remove all special lumia files from the given directory
sub clean_files {
my $dir = shift;
my $match_lumia_files = sub {
@@ -116,6 +130,11 @@ sub clean_files {
}
}
+# read a file, processing each line with $handle_cksum_func if set
+# and writing the results into $cksums
+# $handle_cksum_func must return two values, the checksum of the
+# argument and the rest of the string (that is then parsed for
+# the filename); if it returns undef, this function also returns undef
sub read_file {
my ($file, $cksums, $handle_cksum_func) = @_;
my $fh;
@@ -164,6 +183,7 @@ sub read_file {
return $cksums;
}
+# read a single checksum file, writing the checksums into the hash $cksums and returning it
sub read_cksum_file {
my ($file, $cksums) = @_;
return read_file $file, $cksums, sub {
@@ -179,6 +199,7 @@ sub read_cksum_file {
};
}
+# read the checksums and directory names in $dir
sub read_cksums {
my $dir = shift;
my $cksums = read_cksum_file("$dir/.lumidify_archive_cksums", {});
@@ -188,6 +209,8 @@ sub read_cksums {
return $cksums;
}
+# get the checksum output for $path
+# returns undef if $CKSUM_CMD returns an error
sub get_cksum {
my $path = shift;
my $path_esc = shell_quote $path;
@@ -200,6 +223,8 @@ sub get_cksum {
return $cksum_output;
}
+# check the checksums in $dir/$cksum_file
+# if $quiet is set, only print failed files
sub check_cksums {
my ($dir, $cksum_file, $quiet) = @_;
my $cksums = read_cksum_file("$dir/$cksum_file", {});
@@ -219,6 +244,7 @@ sub check_cksums {
return $failed;
}
+# check the checksums of all files in $top_dir
sub check_files {
my $top_dir = shift;
my $iter = make_lumia_iter $top_dir;
@@ -228,6 +254,8 @@ sub check_files {
}
}
+# write the checksums of the special lumia files given as arguments
+# to ".lumidify_archive_cksums.cksum" in $dir
sub write_special_cksums {
my ($dir, @files) = @_;
my $cksum_file = "$dir/.lumidify_archive_cksums.cksum";
@@ -244,6 +272,13 @@ sub write_special_cksums {
write_file($cksum_file, $cksums, 1);
}
+# search for new files that aren't present in the checksum files
+# - if $file_func is set, it is called for each new file
+# - if $before_dir_func is set, it is called before processing the
+# files in each directory that has new files OR if a directory
+# is entirely new (well, it only checks if ".lumidify_archive_cksums.cksum" exists)
+# - if $after_dir_func is set, it is called after processing the
+# files in each directory that has new files
sub check_new_files {
my ($dir, $file_func, $before_dir_func, $after_dir_func) = @_;
my $iter = make_file_iter sub {-d $_[0]}, sub {
@@ -294,6 +329,7 @@ sub check_new_files {
while ($iter->()) {}
}
+# add all new files in $top_dir to the checksum files
sub check_add_new_files {
my $top_dir = shift;
my $changed_dirs = 0;
@@ -347,6 +383,9 @@ sub check_add_new_files {
};
}
+# write the "checksums" in $contents to $path
+# if $is_cksum_file is set, the value each of the keys in $contents points
+# to is written before the key
sub write_file {
my ($path, $contents, $is_cksum_file) = @_;
my $fh;
@@ -363,11 +402,16 @@ sub write_file {
close $fh;
}
+# write the checksums in $contents to the file at $path
sub write_cksum_file {
my ($path, $contents) = @_;
write_file $path, $contents, 1;
}
+# write the checksums in $contents to $dir
+# any keys that point to undef are taken to be directories and vice versa
+# $files_modified and $dirs_modified control which of the special lumia
+# files actually get written
sub write_cksums {
my ($dir, $contents, $files_modified, $dirs_modified) = @_;
# No, this isn't efficient...
@@ -383,6 +427,7 @@ sub write_cksums {
}
}
+# show all files that are present in the checksum files but don't exist on the filesystem anymore
sub check_old_files {
my $top_dir = shift;
my $iter = make_lumia_iter $top_dir;
@@ -399,6 +444,8 @@ sub check_old_files {
}
}
+# clean up the lumia checksum files, removing any files that aren't present
+# on the filesystem anymore
sub remove_old_files {
my $top_dir = shift;
my $iter = make_lumia_iter $top_dir;
@@ -431,6 +478,10 @@ sub remove_old_files {
}
}
+# sort the given paths into hash based on the dirname
+# returns: a hash with the keys being the dirnames of the given paths and
+# each one pointing to an array containing the basenames of all paths
+# that had this dirname
sub sort_by_dir {
my %sorted_files;
foreach my $file (@_) {
@@ -447,6 +498,7 @@ sub sort_by_dir {
return \%sorted_files;
}
+# copies the $src files to $dst and updates the checksums in $dst
# $src: list of source paths
# $dst: destination directory or file (in latter case only one src is allowed)
sub copy_files {
@@ -494,14 +546,15 @@ sub copy_files {
write_cksums $dst_dir, $dst_cksums, $files_touched, $dirs_touched;
}
+# return whether the two paths are the same
sub cmp_path {
my ($src, $dst) = @_;
- # remove trailing slash so compare works
- $src =~ s/\/$//;
- $dst =~ s/\/$//;
- return $src eq $dst;
+ my $src_real = realpath $src;
+ return defined $src_real && $src_real eq realpath $dst;
}
+# move a file from $src to $dst, prompting for confirmation if $dst already exists
+# automatically appends the basename of $src to $dst if $dst is a directory
sub move_file {
my ($src, $dst) = @_;
if (-d $dst) {
@@ -519,6 +572,9 @@ sub move_file {
return 0;
}
+# move all files/directories in $src_files from $src_dir to $dst_dir ($src_files
+# only contains the basenames of the files), removing them from the checksum files
+# in $src_dir and adding them to $dst_cksums
sub move_from_same_dir {
my ($src_dir, $src_files, $dst_cksums, $dst_dir) = @_;
my $src_cksums = read_cksums $src_dir;
@@ -531,15 +587,21 @@ sub move_from_same_dir {
warn "ERROR: can't move \"$fullpath\" into \"$dst_dir\" (same dir)\n";
next;
}
+ my $tmp_dirs_touched = 0;
+ my $tmp_files_touched = 0;
+ if (-d $fullpath) {
+ $tmp_dirs_touched = 1;
+ } else {
+ $tmp_files_touched = 1;
+ }
if (my $err = move_file($fullpath, $dst_dir)) {
warn "$err\n";
next;
}
- if (-d $fullpath) {
- $dirs_touched = 1;
- } else {
- $files_touched = 1;
- }
+ # need to be able to check if the path is a directory
+ # before actually moving it
+ $dirs_touched ||= $tmp_dirs_touched;
+ $files_touched ||= $tmp_files_touched;
if (exists $src_cksums->{$src_file}) {
$dst_cksums->{$src_file} = $src_cksums->{$src_file};
delete $src_cksums->{$src_file};
@@ -551,6 +613,7 @@ sub move_from_same_dir {
return ($files_touched, $dirs_touched);
}
+# rename a single file or directory from $src to $dst
sub move_rename {
my ($src, $dst) = @_;
my $src_dir = dirname $src;
@@ -594,6 +657,12 @@ sub move_rename {
}
}
+# move all files and directories in $src to $dst
+# - if $dst does not exist, $src is only allowed to contain one path, which is
+# renamed to $dst
+# - if $dst is a file, $src is only allowed to contain a single path (which
+# must be a file), which is renamed to $dst
+# - otherwise, all files and directories in $src are moved to $dst
# $src: list of source paths
# $dst: destination directory or file (in latter case only one src is allowed)
sub move_files {
@@ -631,6 +700,7 @@ sub move_files {
write_cksums $dst, $dst_cksums, $files_touched, $dirs_touched;
}
+# remove a file or directory from the filesystem
sub remove_file_dir {
my $path = shift;
if (-d $path) {
@@ -641,6 +711,9 @@ sub remove_file_dir {
return 0;
}
+# remove all files in one directory, updating the checksum files in the process
+# note: the files are only allowed to be basenames, i.e., they must be the
+# actual filenames present in the checksum files
sub remove_from_same_dir {
my ($dir, @files) = @_;
my $cksums = read_cksums $dir;
@@ -670,6 +743,8 @@ sub remove_from_same_dir {
write_cksums $dir, $cksums, $files_touched, $dirs_touched;
}
+# remove all given files and directories, updating the appropriate checksum
+# files in the process
sub remove_files {
my $sorted_files = sort_by_dir(@_);
foreach my $dir (keys %$sorted_files) {
@@ -677,6 +752,9 @@ sub remove_files {
}
}
+# create the given directories, initializing them with empty checksum files
+# note: does not work like "mkdir -p", i.e., the new directories have to
+# be located inside already existing directories
sub make_dirs {
my @created_dirs;
foreach (@_) {
@@ -726,7 +804,7 @@ sub extract {
}
if ($#ARGV < 0) {
- die("USAGE: test.pl {init|check|clean|checknew|addnew|cp|mv|rm|mkdir}\n");
+ die("USAGE: test.pl {init|check|clean|checknew|addnew|checkold|rmold|extract|cp|mv|rm|mkdir}\n");
}
if ($ARGV[0] eq "mv") {
if ($#ARGV < 2) {
diff --git a/test/.lumidify_archive_cksums.cksum b/test/.lumidify_archive_cksums.cksum
@@ -1,2 +1,2 @@
2507213385 41 ".lumidify_archive_cksums"
-1201997706 27 ".lumidify_archive_dirs"
+3971863640 21 ".lumidify_archive_dirs"
diff --git a/test/.lumidify_archive_dirs b/test/.lumidify_archive_dirs
@@ -1,4 +1,3 @@
-"dir"
"dir2"
"dir3"
"dir4"
diff --git a/test/dir2/.lumidify_archive_cksums.cksum b/test/dir2/.lumidify_archive_cksums.cksum
@@ -1,2 +1,2 @@
4294967295 0 ".lumidify_archive_cksums"
-4294967295 0 ".lumidify_archive_dirs"
+137730780 6 ".lumidify_archive_dirs"
diff --git a/test/dir2/.lumidify_archive_dirs b/test/dir2/.lumidify_archive_dirs
@@ -0,0 +1 @@
+"dir"
diff --git a/test/dir/.lumidify_archive_cksums b/test/dir2/dir/.lumidify_archive_cksums
diff --git a/test/dir/.lumidify_archive_cksums.cksum b/test/dir2/dir/.lumidify_archive_cksums.cksum
diff --git a/test/dir/.lumidify_archive_dirs b/test/dir2/dir/.lumidify_archive_dirs
diff --git a/test/dir/meh b/test/dir2/dir/meh