commit b2a3055319a2c8e8d2ba0f0ba05411c4d5dcf746
Author: lumidify <nobody@lumidify.org>
Date: Fri, 21 Feb 2020 13:13:41 +0100
Initial Commit (old buggy code)
Diffstat:
A | TODO | | | 2 | ++ |
A | lumia.pl | | | 829 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
2 files changed, 831 insertions(+), 0 deletions(-)
diff --git a/TODO b/TODO
@@ -0,0 +1,2 @@
+Allow to run command on multiple files but keep checksum (e.g. convmv)
+update command - if file was edited
diff --git a/lumia.pl b/lumia.pl
@@ -0,0 +1,829 @@
+#!/usr/bin/env perl
+
+# FIXME: add ckold to check what files don't exist anymore and remove them from cksums
+# FIXME: write cksums as we go along during check_add_new, not just at end
+# FIXME: some way to avoid writing .lumidify* in dirs but still index them? e.g. Code/CMSG
+# FIXME: cksum don't create malformed line if permission denied
+# FIXME: dirs in ignore still created when extracting
+# FIXME: make generic function to traverse dirs and call other function on each dir
+# FIXME: extract - actually copy files, don't write again and regen cksums.cksum
+# FIXME: check cksums.cksum when reading files
+# FIXME: check if .lumidify* are writable first so cksums aren't generated uselessly
+# FIXME: handle rm, etc. on .lumidify* files
+# FIXME: handle files with newline?
+# FIXME: ignore all except for a certain file/folder
+# FIXME: store modified date and recksum filed with changed date
+# FIXME: sha256 works with leading zeroes, maybe switch?
+# FIXME: allow different hash types
+# FIXME: don't write anything if cksum fails (will create malformed line)
+# FIXME: add option to just check dir structure or maybe check if everything exists
+# WARNING: convert regenerates cksums.cksum
+# FIXME: add option to compare cksums of two dirs
+# FIXME: add option to check only individual files
+
+use strict;
+use warnings;
+use File::Spec::Functions qw(catfile catdir splitpath splitdir);
+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 POSIX qw(SIGINT);
+use Data::Dumper;
+use Scalar::Util qw(looks_like_number);
+
+my $CKSUM_CMD = 'cksum -q';
+my $CKSUM_CHECK_CMD = 'cksum -c';
+my $CKSUM_CHECK_SINGLE_CMD = 'cksum -q';
+
+sub clean_files {
+ my @files = (
+ ".lumidify_archive_cksums",
+ ".lumidify_archive_cksums.cksum",
+ ".lumidify_archive_ignore",
+ ".lumidify_archive_dirs"
+ );
+ for my $file (@files) {
+ if (-e $file) {
+ my $dir = getcwd();
+ unlink($file, {safe => 1}) or return "ERROR: can't remove \"$dir/$file\": $!";
+ print("Deleted \"$dir/$file\"\n");
+ }
+ }
+ opendir(my $dh, ".") or die("ERROR: Unable to list files in \"" . getcwd() . "\"\n");
+ my @dirs;
+ my $file;
+ while ($file = readdir($dh)) {
+ next if ($file =~ /\A\.\.?\z/);
+ # Just so these files aren't all left open while recursing
+ if (-d $file) {
+ push(@dirs, $file);
+ }
+ }
+ closedir($dh);
+ my $dir = getcwd();
+ foreach (@dirs) {
+ chdir($_);
+ clean_files();
+ chdir($dir);
+ }
+}
+
+sub read_file {
+ my ($file, $is_cksums_file) = @_;
+ my $path = catfile(getcwd(), $file);
+ my %cksums;
+ my $fh;
+ if (!open($fh, "<", $file)) {
+ print(STDERR "ERROR: unable to open file \"$path\": $!\n");
+ return;
+ }
+ my $in_fn = 0;
+ my $cur_cksum;
+ my $cur_str;
+ my $cur_fn = "";
+ foreach (<$fh>) {
+ next if (!$in_fn && /^$/);
+ if ($is_cksums_file && !$in_fn) {
+ my @fields = split(/ /, $_, 3);
+ if ($#fields != 2) {
+ print(STDERR "ERROR: malformed line \"$_\" in file \"$path\"\n");
+ next;
+ }
+ $cur_cksum = join(" ", @fields[0,1]);
+ $cur_str = $fields[2];
+ } else {
+ $cur_str = $_;
+ }
+ my $bs = 0;
+ foreach my $ch (split(//, $cur_str)) {
+ if ($ch eq "\\") {
+ $bs++;
+ $cur_fn .= "\\" if !($bs %= 2) && $in_fn;
+ } elsif ($bs % 2) {
+ $cur_fn .= $ch if $in_fn;
+ $bs = 0;
+ } elsif ($ch eq "\"") {
+ if ($in_fn) {
+ $in_fn = 0;
+ $cksums{$cur_fn} = $cur_cksum;
+ $cur_fn = "";
+ last;
+ }
+ $in_fn = 1;
+ } elsif ($in_fn) {
+ $cur_fn .= $ch;
+ }
+ }
+ }
+ if ($in_fn) {
+ print(STDERR "ERROR: unterminated filename in file \"$path\"\n");
+ }
+ return \%cksums;
+}
+
+# This would be much cleaner with cksums's -c option, but that
+# doesn't seem to work with files beginning with whitespace
+sub check_cksums {
+ my $cksum_file = shift;
+ my $cksums = read_file($cksum_file, 1);
+ foreach my $file (keys %$cksums) {
+ my $file_esc = shell_quote($file);
+ my $output = `$CKSUM_CHECK_SINGLE_CMD -- $file_esc 2>&1`;
+ my $path = catfile(getcwd(), $file);
+ if ($?) {
+ print(STDERR "ERROR getting cksum for file \"$path\":\n$output\n");
+ next;
+ }
+ chomp($output);
+ print(($output eq $cksums->{$file} ? "OK" : "FAILED") . " $path\n");
+ }
+ return;
+ my $fullpath = catfile(getcwd(), $cksum_file);
+ my $fh;
+ if (!open($fh, "<", $cksum_file)) {
+ print("ERROR: unable to open cksum file \"$fullpath\": $!\n");
+ return;
+ }
+ foreach (<$fh>) {
+ chomp;
+ next if !$_;
+ my @fields = split(/ /, $_, 3);
+ if ($#fields != 2) {
+ print("ERROR: malformed line \"$_\" in file \"$fullpath\"\n");
+ next;
+ }
+ my $cksum = join(" ", @fields[0,1]);
+ my $file = shell_quote($fields[2]);
+ # '--' needed so it works on files with leading '-'
+ my $output = `$CKSUM_CHECK_SINGLE_CMD -- $file`;
+ my $path = catfile(getcwd(), $fields[2]);
+ if ($?) {
+ print("ERROR getting cksum for file \"$path\":\n$output\n");
+ next;
+ }
+ chomp($output);
+ print(($output eq $cksum ? "OK" : "FAILED") . " $path\n");
+ }
+ close($fh);
+}
+
+sub check_files {
+ check_cksums(".lumidify_archive_cksums.cksum");
+ check_cksums(".lumidify_archive_cksums");
+ #my $output = `$CKSUM_CHECK_CMD .lumidify_archive_cksums.cksum`;
+ #if ($?) {
+ # print("ERROR in directory \"" . getcwd() . "\"\n");
+ #}
+ # It would be better to do this properly with cksum printing its output
+ # directly, but I couldn't get system() to work with quitting if SIGINT
+ # is received (yes, I followed the instructions in perldoc -f system)
+ #print($output);
+ #if (-s ".lumidify_archive_cksums") {
+ # $output = `$CKSUM_CHECK_CMD .lumidify_archive_cksums`;
+ # if ($?) {
+ # print("ERROR in directory \"" . getcwd() . "\"\n");
+ # }
+ # print($output);
+ #}
+ return if (!-f ".lumidify_archive_dirs");
+ my $dirs = read_file(".lumidify_archive_dirs");
+ foreach my $dir (keys %$dirs) {
+ my $cwd = getcwd();
+ if (!-d $dir) {
+ print("ERROR: directory \"$cwd/$dir\" does not exist anymore.\n");
+ next;
+ }
+ chdir($dir);
+ check_files();
+ chdir($cwd);
+ }
+ return;
+ open(my $fh, "<", ".lumidify_archive_dirs") or die("ERROR: Unable to open \"" . getcwd() . "/.lumidify_archive_dirs\"\n");
+ my @dirs;
+ foreach (<$fh>) {
+ chomp;
+ next if (!$_);
+ # Just so these files aren't all left open while recursing
+ push(@dirs, $_);
+ }
+ close($fh);
+ foreach (@dirs) {
+ my $dir = getcwd();
+ if (!-d $_) {
+ print("ERROR: directory \"$dir/$_\" does not exist anymore.\n");
+ next;
+ }
+ chdir($_);
+ check_files();
+ chdir($dir);
+ }
+}
+
+sub check_new_files_recurse {
+ my $dir = shift;
+ my $cur_hash = shift;
+ my $cur_cksum_hash = shift;
+ #my %ignore;
+ my $ignore = {};
+ if (-f catfile($dir, '.lumidify_archive_ignore')) {
+ $ignore = read_file(catfile($dir, ".lumidify_archive_ignore"));
+ }
+=pod
+ open(my $fh, "<", catfile($dir, '.lumidify_archive_ignore')) or die("Can't open $dir/.lumidify_archive_ignore: $!\n");
+ foreach (<$fh>) {
+ chomp;
+ $ignore{$_} = undef;
+ }
+ close($fh);
+ }
+=cut
+ opendir(my $dh, $dir) or die("Can't open $dir: $!\n");
+ my $file;
+ my $fullpath;
+ while ($file = readdir($dh)) {
+ next if ($file =~ /\A\.\.?\z/);
+ next if (exists($ignore->{$file}));
+ next if ($file eq '.lumidify_archive_cksums' ||
+ $file eq '.lumidify_archive_cksums.cksum' ||
+ $file eq '.lumidify_archive_dirs' ||
+ $file eq '.lumidify_archive_ignore');
+ $fullpath = catfile($dir, $file);
+ if (-d $fullpath) {
+ $cur_hash->{$file} = {};
+ if (!defined($cur_cksum_hash) || !exists($cur_cksum_hash->{$file})) {
+ check_new_files_recurse($fullpath, $cur_hash->{$file}, undef);
+ } else {
+ check_new_files_recurse($fullpath, $cur_hash->{$file}, $cur_cksum_hash->{$file});
+ }
+ } else {
+ if (!defined($cur_cksum_hash) || !exists($cur_cksum_hash->{$file})) {
+ $cur_hash->{$file} = undef;
+ }
+ }
+ }
+ closedir($dh);
+}
+
+sub check_new_files {
+ my $dir = shift;
+ my $cksum_tree = shift;
+ my %new_hash;
+ check_new_files_recurse($dir, \%new_hash, $cksum_tree);
+ return \%new_hash;
+}
+
+sub add_new_files_recurse {
+ my $dir = shift;
+ my $cur_hash = shift;
+ my $cur_cksum_hash = shift;
+ my $fullpath;
+ my $cksum_output;
+ foreach my $file (keys(%$cur_hash)) {
+ $fullpath = catfile($dir, $file);
+ if (-d $fullpath) {
+ if (!exists($cur_cksum_hash->{$file})) {
+ $cur_cksum_hash->{$file} = {};
+ }
+ add_new_files_recurse($fullpath, $cur_hash->{$file}, $cur_cksum_hash->{$file});
+ } else {
+ my $path_esc = shell_quote($fullpath);
+ $cksum_output = `$CKSUM_CMD -- $path_esc`;
+ chomp($cksum_output);
+ # TODO: check for error
+ $cur_cksum_hash->{$file} = $cksum_output;
+ print("Added checksum: $cksum_output $fullpath\n");
+ }
+ }
+}
+
+sub add_new_files {
+ my $dir = shift;
+ my $new_files = shift;
+ my $cksum_tree = shift;
+ add_new_files_recurse($dir, $new_files, $cksum_tree);
+}
+
+sub check_add_new_files {
+ my $dir = shift;
+ my $cksum_tree = shift;
+ my $new_files = check_new_files($dir, $cksum_tree);
+ add_new_files($dir, $new_files, $cksum_tree);
+ write_cksums($dir, $cksum_tree);
+}
+
+sub read_cksums_old {
+ my ($cksums, $dir, $recurse) = @_;
+ my $cksums_file = catfile($dir, '.lumidify_archive_cksums');
+ if (!-f $cksums_file) {
+ die("No cksum file '.lumidify_archive_cksums' in directory $dir\n");
+ }
+ open(my $fh, "<", $cksums_file) or die("Can't open $cksums_file: $!\n");
+ foreach (<$fh>) {
+ chomp;
+ next if (!$_);
+ my @fields = split(/ /, $_, 3);
+ if ($#fields != 2 || !looks_like_number($fields[0])) {
+ print("ERROR: Malformed line in $cksums_file:\n$_\n");
+ next;
+ }
+ $cksums->{$fields[2]} = $fields[0] . " " . $fields[1];
+ }
+ close($fh);
+ my $dirs_file = catfile($dir, '.lumidify_archive_dirs');
+ return if (!-f $dirs_file);
+ open($fh, "<", $dirs_file) or die("Can't open $dirs_file: $!\n");
+ foreach (<$fh>) {
+ chomp;
+ next if (!$_);
+ my $fulldir = catdir($dir, $_);
+ if (!-d $fulldir) {
+ print(STDERR "WARNING: Directory \"$fulldir\" mentioned in \"$dirs_file\" does not exist!\n");
+ }
+ $cksums->{$_} = {};
+ if ($recurse) {
+ read_cksums_old($cksums->{$_}, $fulldir, 1);
+ }
+ }
+ close($fh);
+}
+
+sub read_cksums {
+ my ($cksums, $dir, $recurse) = @_;
+ my $cksums_file = catfile($dir, '.lumidify_archive_cksums');
+ if (!-f $cksums_file) {
+ die("No cksum file '.lumidify_archive_cksums' in directory $dir\n");
+ }
+ my $tmp_cksums = read_file($cksums_file, 1);
+ foreach (keys %$tmp_cksums) {
+ $cksums->{$_} = $tmp_cksums->{$_};
+ }
+ #$cksums->{keys %$tmp_cksums} = @{$tmp_cksums->{keys %$tmp_cksums}};
+=pod
+ open(my $fh, "<", $cksums_file) or die("Can't open $cksums_file: $!\n");
+ foreach (<$fh>) {
+ chomp;
+ next if (!$_);
+ my @fields = split(/ /, $_, 3);
+ if ($#fields != 2) {
+ print("ERROR: Malformed line in $cksums_file:\n$_\n");
+ next;
+ }
+ $cksums->{$fields[2]} = $fields[0] . " " . $fields[1];
+ }
+ close($fh);
+=cut
+ my $dirs_file = catfile($dir, '.lumidify_archive_dirs');
+ return if (!-f $dirs_file);
+ my $dirs = read_file($dirs_file);
+ foreach (keys %$dirs) {
+ my $fulldir = catdir($dir, $_);
+ if (!-d $fulldir) {
+ print(STDERR "WARNING: Directory \"$fulldir\" mentioned in \"$dirs_file\" does not exist!\n");
+ }
+ $cksums->{$_} = {};
+ if ($recurse) {
+ read_cksums($cksums->{$_}, $fulldir, 1);
+ }
+ }
+=pod
+ open($fh, "<", $dirs_file) or die("Can't open $dirs_file: $!\n");
+ foreach (<$fh>) {
+ chomp;
+ next if (!$_);
+ my $fulldir = catdir($dir, $_);
+ if (!-d $fulldir) {
+ print(STDERR "WARNING: Directory \"$fulldir\" mentioned in \"$dirs_file\" does not exist!\n");
+ }
+ $cksums->{$_} = {};
+ if ($recurse) {
+ read_cksums($cksums->{$_}, $fulldir, 1);
+ }
+ }
+ close($fh);
+=cut
+}
+
+sub init_cksums_old {
+ my $dir = shift;
+ # avoid catfile turning it into an absolute path if $dir is an empty string
+ if (!$dir) {
+ $dir = ".";
+ }
+ my %cksums;
+ read_cksums_old(\%cksums, $dir, 1);
+ return \%cksums;
+}
+
+sub init_cksums {
+ my $dir = shift;
+ # avoid catfile turning it into an absolute path if $dir is an empty string
+ if (!$dir) {
+ $dir = ".";
+ }
+ my %cksums;
+ read_cksums(\%cksums, $dir, 1);
+ return \%cksums;
+}
+
+# TODO: possibly sort files before dumping in files?
+sub write_cksums {
+ my ($dir, $cur_cksum_hash, $recurse, $create_dirs) = @_;
+ # FIXME: error checking
+ if ($create_dirs) {
+ make_path($dir);
+ }
+ my $cksums_path = catfile($dir, ".lumidify_archive_cksums");
+ my $dirs_path = catfile($dir, ".lumidify_archive_dirs");
+ open(my $cksumsfh, ">", $cksums_path) or die("Can't open \"$cksums_path\" for writing: $!\n");
+ open(my $dirsfh, ">", $dirs_path) or die("Can't open \"$dirs_path\" for writing: $!\n");
+ foreach my $file (keys %$cur_cksum_hash) {
+ my $file_esc = $file;
+ $file_esc =~ s/\\/\\\\/g;
+ $file_esc =~ s/"/\\"/g;
+ if (ref($cur_cksum_hash->{$file}) eq "HASH") {
+ print($dirsfh "\"$file_esc\"\n");
+ } else {
+ print($cksumsfh "$cur_cksum_hash->{$file} \"$file_esc\"\n");
+ }
+ }
+ close($cksumsfh);
+ close($dirsfh);
+
+ open(my $fh, ">", "$cksums_path.cksum") or die("Cannot open $cksums_path.cksum for writing\n");
+ my $cksums_path_esc = shell_quote($cksums_path);
+ my $dirs_path_esc = shell_quote($dirs_path);
+ my $cksums_cksum = `$CKSUM_CMD $cksums_path_esc`;
+ my $dirs_cksum = `$CKSUM_CMD $dirs_path_esc`;
+ chomp($cksums_cksum);
+ chomp($dirs_cksum);
+ print($fh "$cksums_cksum \".lumidify_archive_cksums\"\n");
+ print($fh "$dirs_cksum \".lumidify_archive_dirs\"\n");
+ if (-f catfile($dir, '.lumidify_archive_ignore')) {
+ my $ignore_path_esc = shell_quote(catfile($dir, '.lumidify_archive_ignore'));
+ my $ignore_cksum = `$CKSUM_CMD $ignore_path_esc`;
+ chomp $ignore_cksum;
+ print($fh "$ignore_cksum \".lumidify_archive_ignore\"\n");
+ }
+ close($fh);
+
+ # For e.g. moving, we don't want to read and write all cksums since
+ # everything below the level being moved stays the same
+ return if (!$recurse);
+ # Use second pass for this so the files don't all stay open
+ foreach my $file (keys %$cur_cksum_hash) {
+ if (ref($cur_cksum_hash->{$file}) eq "HASH") {
+ write_cksums(catdir($dir, $file), $cur_cksum_hash->{$file}, 1, $create_dirs);
+ }
+ }
+}
+
+# $src: list of source paths
+# $dst: destination directory or file (in latter case only one src is allowed)
+sub copy_files {
+ my $src = shift;
+ my $dst = shift;
+ my $dst_dir = $dst;
+ if (!-d $dst) {
+ $dst_dir = dirname($dst);
+ }
+ my $diff_name = 0;
+ if (!-d $dst && !-d $src->[0]) {
+ $diff_name = 1;
+ }
+ if (!-e $dst && -d $src->[0]) {
+ $diff_name = 1;
+ }
+ if (system("cp", "-aiv", @$src, $dst)) {
+ die("ERROR while copying files\n");
+ }
+ # Separate files by current dir so the cksum and dir files only need to be opened once
+ my %src_files;
+ foreach my $src_file (@$src) {
+ my $dir = dirname($src_file);
+ if (!exists($src_files{$dir})) {
+ $src_files{$dir} = [];
+ }
+ push(@{$src_files{$dir}}, basename($src_file));
+ }
+ my %dst_cksums;
+ read_cksums(\%dst_cksums, $dst_dir, 0);
+ foreach my $src_dir (keys %src_files) {
+ my %src_cksums;
+ read_cksums(\%src_cksums, $src_dir, 0);
+ foreach my $src_file (@{$src_files{$src_dir}}) {
+ if (exists($src_cksums{$src_file})) {
+ if ($diff_name) {
+ $dst_cksums{basename($dst)} = $src_cksums{$src_file};
+ } else {
+ $dst_cksums{$src_file} = $src_cksums{$src_file};
+ }
+ } else {
+ print(STDERR "WARNING: \"$dst_dir/$src_file\" not in cksum or directory list.\n");
+ }
+ }
+ }
+ write_cksums($dst_dir, \%dst_cksums, 0);
+}
+
+sub move_file {
+ my ($src, $dst) = @_;
+ if (-d $dst) {
+ $dst = catdir($dst, basename($src));
+ }
+ if (-e $dst) {
+ print(STDERR "WARNING: \"$dst\" exists already. Do you want to replace it? ");
+ my $choice = <STDIN>;
+ chomp $choice;
+ if ($choice ne "y" && $choice ne "Y") {
+ return "Not moving \"$src\" to \"$dst\".";
+ }
+ }
+ move($src, $dst) or return "ERROR: can't move \"$src\" to \"$dst\": $!";
+ return 0;
+}
+
+sub move_from_same_dir {
+ my ($src_dir, $src_files, $dst_dir) = @_;
+ my %src_cksums;
+ read_cksums(\%src_cksums, $src_dir, 0);
+ my %dst_cksums;
+ read_cksums(\%dst_cksums, $dst_dir, 0);
+ foreach my $src_file (@$src_files) {
+ my $fullpath = catfile($src_dir, $src_file);
+ if (my $err = move_file($fullpath, $dst_dir)) {
+ print(STDERR "$err\n");
+ next;
+ }
+ if (exists($src_cksums{$src_file})) {
+ $dst_cksums{$src_file} = $src_cksums{$src_file};
+ delete($src_cksums{$src_file});
+ } else {
+ print(STDERR "WARNING: \"$dst_dir/$src_file\" not in cksum or directory list.\n");
+ }
+ }
+ write_cksums($src_dir, \%src_cksums, 0);
+ write_cksums($dst_dir, \%dst_cksums, 0);
+}
+
+sub move_rename {
+ my ($src, $dst) = @_;
+ my $src_dir = dirname($src);
+ my $dst_dir = dirname($dst);
+ my $src_file = basename($src);
+ my $dst_file = basename($dst);
+ my %src_cksums;
+ read_cksums(\%src_cksums, $src_dir, 0);
+ my %dst_cksums;
+ # if a file is simply being renamed in the same dir, the cksums
+ # should only be loaded and written once
+ if ($src_dir eq $dst_dir) {
+ %dst_cksums = %src_cksums;
+ delete($dst_cksums{$src_file});
+ } else {
+ read_cksums(\%dst_cksums, $dst_dir, 0);
+ }
+ if (my $err = move_file($src, $dst)) {
+ print(STDERR "$err\n");
+ return;
+ }
+ if (exists($src_cksums{$src_file})) {
+ $dst_cksums{$dst_file} = $src_cksums{$src_file};
+ delete($src_cksums{$src_file});
+ } else {
+ print(STDERR "WARNING: \"$dst\" not in cksum or directory list.\n");
+ }
+ if ($src_dir ne $dst_dir) {
+ write_cksums($src_dir, \%src_cksums, 0);
+ }
+ write_cksums($dst_dir, \%dst_cksums, 0);
+}
+
+sub cmp_path {
+ my ($src, $dst) = @_;
+ # remove trailing slash so compare works
+ $src =~ s/\/$//;
+ $dst =~ s/\/$//;
+ return $src eq $dst;
+}
+
+# $src: list of source paths
+# $dst: destination directory or file (in latter case only one src is allowed)
+sub move_files {
+ my $src = shift;
+ my $dst = shift;
+ foreach my $src_file (@$src) {
+ if ($src_file eq ".") {
+ die("Can't move current directory (.)\n");
+ }
+ }
+ if (!-d $dst && $#$src != 0) {
+ die("move: only one src argument allowed when dst is a file\n");
+ }
+ if (!-d $dst && !-d $src->[0]) {
+ move_rename($src->[0], $dst);
+ return;
+ }
+ if (!-e $dst && -d $src->[0]) {
+ move_rename($src->[0], $dst);
+ return;
+ }
+ if (-e $dst && !-d $dst && -d $src->[0]) {
+ die("move: can't move directory to file\n");
+ }
+ # Separate files by current dir so the cksum and dir files only need to be opened once
+ my %src_files;
+ foreach my $src_file (@$src) {
+ if (!-e $src_file) {
+ die("Source file \"$src_file\" doesn't exist.\n");
+ }
+ if (cmp_path($src_file, $dst)) {
+ print(STDERR "ERROR: can't move \"$src_file\" into \"$dst\" (same dir)\n");
+ next;
+ }
+ my $dir = dirname($src_file);
+ if (!exists($src_files{$dir})) {
+ $src_files{$dir} = [];
+ }
+ push(@{$src_files{$dir}}, basename($src_file));
+ }
+ foreach my $src_dir (keys %src_files) {
+ move_from_same_dir($src_dir, $src_files{$src_dir}, $dst);
+ }
+}
+
+sub remove_file_dir {
+ my $path = shift;
+ if (-d $path) {
+ remove_tree($path, {safe => 1}) or return "ERROR: can't remove \"$path\": $!";
+ } else {
+ unlink($path, {safe => 1}) or return "ERROR: can't remove \"$path\": $!";
+ }
+ return 0;
+}
+
+sub remove_from_same_dir {
+ my ($dir, $files) = @_;
+ my %cksums;
+ read_cksums(\%cksums, $dir, 0);
+ foreach my $file (@$files) {
+ my $fullpath = catfile($dir, $file);
+ if (!-e $fullpath) {
+ print(STDERR "\"$fullpath\": No such file or directory.\n");
+ }
+ if (my $err = remove_file_dir($fullpath)) {
+ print(STDERR "$err\n");
+ next;
+ }
+ if (exists($cksums{$file})) {
+ delete($cksums{$file});
+ } else {
+ print(STDERR "WARNING: \"$file\" not in cksum or directory list.\n");
+ }
+ }
+ write_cksums($dir, \%cksums, 0);
+}
+
+sub remove_files {
+ my $files = shift;
+ my %sorted_files;
+ foreach my $file (@$files) {
+ my $dir = dirname($file);
+ if (!exists($sorted_files{$dir})) {
+ $sorted_files{$dir} = [];
+ }
+ push(@{$sorted_files{$dir}}, basename($file));
+ }
+ foreach my $dir (keys %sorted_files) {
+ remove_from_same_dir($dir, $sorted_files{$dir});
+ }
+}
+
+sub make_dirs {
+ my @created_dirs;
+ foreach (@_) {
+ if (system("mkdir", $_)) {
+ print(STDERR "ERROR creating directory $_\n");
+ }
+ push(@created_dirs, $_);
+ }
+ # Separate files by current dir so the cksum and dir files only need to be opened once
+ my %dirs;
+ foreach my $dir (@created_dirs) {
+ # FiXME: seems a bit ugly (must write cksums in new dir for it to work later)
+ write_cksums($dir, {}, 0);
+ my $parent = dirname($dir);
+ if (!exists($dirs{$parent})) {
+ $dirs{$parent} = [];
+ }
+ push(@{$dirs{$parent}}, basename($dir));
+ }
+ foreach my $parent (keys %dirs) {
+ my %cksums;
+ read_cksums(\%cksums, $parent, 0);
+ foreach my $dir (@{$dirs{$parent}}) {
+ $cksums{$dir} = {};
+ }
+ write_cksums($parent, \%cksums, 0);
+ }
+}
+
+if ($#ARGV < 0) {
+ die("USAGE: test.pl {init|check|clean|cknew|addnew|cp|mv|rm|mkdir}\n");
+}
+if ($ARGV[0] eq "mv") {
+ if ($#ARGV < 2) {
+ die "mv requires at least two arguments\n";
+ }
+ my @src = @ARGV[1..$#ARGV-1];
+ move_files(\@src, $ARGV[-1]);
+} elsif ($ARGV[0] eq "rm") {
+ if ($#ARGV < 1) {
+ die "rm requires at least one argument\n";
+ }
+ my @files = @ARGV[1..$#ARGV];
+ remove_files(\@files);
+} elsif ($ARGV[0] eq "addnew") {
+ my $dir = ".";
+ if ($#ARGV > 0) {
+ $dir = $ARGV[1];
+ }
+ my $cksums = init_cksums($dir);
+ check_add_new_files($dir, $cksums);
+ write_cksums($dir, $cksums, 1);
+} elsif ($ARGV[0] eq "cknew") {
+ my $dir = ".";
+ if ($#ARGV > 0) {
+ $dir = $ARGV[1];
+ }
+ my $cksums = init_cksums($dir);
+ my $new_files = check_new_files($dir, $cksums);
+ print(Dumper($new_files));
+} elsif ($ARGV[0] eq "init") {
+ my $dir = ".";
+ if ($#ARGV > 0) {
+ $dir = $ARGV[1];
+ }
+ # FIXME: at least first recurse through dirs and check if any contain cksums
+ my $cksums = {};
+ check_add_new_files($dir, $cksums);
+ write_cksums($dir, $cksums, 1);
+} elsif ($ARGV[0] eq "check") {
+ if ($#ARGV > 0) {
+ my $dir = $ARGV[1];
+ if (!-d $dir) {
+ die("ERROR: Directory \"$dir\" does not exist.\n");
+ }
+ chdir($dir);
+ }
+ check_files();
+} elsif ($ARGV[0] eq "clean") {
+ if ($#ARGV > 0) {
+ my $dir = $ARGV[1];
+ if (!-d $dir) {
+ die("ERROR: Directory \"$dir\" does not exist.\n");
+ }
+ chdir($dir);
+ }
+ clean_files();
+} elsif ($ARGV[0] eq "extract") {
+ my $src_dir = ".";
+ my $dst_dir;
+ if ($#ARGV > 1) {
+ $src_dir = $ARGV[1];
+ $dst_dir = $ARGV[2];
+ } elsif ($#ARGV == 1) {
+ $dst_dir = $ARGV[1];
+ } else {
+ die("ERROR: `extract` requires at least a destination directory.\n");
+ }
+ if (!-d $src_dir) {
+ die("ERROR: Directory \"$src_dir\" does not exist.\n");
+ }
+ if (!-d $dst_dir) {
+ die("ERROR: Directory \"$dst_dir\" does not exist.\n");
+ }
+ # FIXME: check .lumidify_archive_cksums.cksum when initializing
+ my $cksums = init_cksums($src_dir);
+ write_cksums($dst_dir, $cksums, 1, 1);
+} elsif ($ARGV[0] eq "cp") {
+ if ($#ARGV < 2) {
+ die "cp requires at least two arguments\n";
+ }
+ my @src = @ARGV[1..$#ARGV-1];
+ copy_files(\@src, $ARGV[-1]);
+} elsif ($ARGV[0] eq "mkdir") {
+ if ($#ARGV < 1) {
+ die "mkdir requires at least one argument\n";
+ }
+ my @dirs = @ARGV[1..$#ARGV];
+ make_dirs(@dirs);
+} elsif ($ARGV[0] eq "convert") {
+ my $dir = ".";
+ if ($#ARGV > 0) {
+ $dir = $ARGV[1];
+ }
+ my $cksums = init_cksums_old($dir);
+ write_cksums($dir, $cksums, 1);
+}