#!/usr/bin/perl
#
# The LearningOnline Network
#
# Compare last modification dates for files in Authoring Space with last
# modification dates for corresponding files in Resource Space.
# If file in Authoring Space is older than file in Resource Space, and 
# file is not a binary file, check if files are the same.
# If files are not the same include in list for potentially overwriting
# file in Authoring space with file in Resource space. 
#
# $Id: check_authoring_spaces.pl,v 1.2 2017/10/10 15:07:45 raeburn Exp $
#
# Copyright Michigan State University Board of Trustees
#
# This file is part of the LearningOnline Network with CAPA (LON-CAPA).
#
# LON-CAPA is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# LON-CAPA is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with LON-CAPA; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
# /home/httpd/html/adm/gpl.txt
#
# http://www.lon-capa.org/
#
#################################################

use strict;
use lib '/home/httpd/lib/perl/';
use LONCAPA::Configuration;
use LONCAPA qw(:DEFAULT :match);
use Apache::lonlocal;
use File::Compare;
use File::Copy;

my ($lonusersdir,$londocroot,$londaemons);

BEGIN {
    my $perlvar=&LONCAPA::Configuration::read_conf();
    if (ref($perlvar) eq 'HASH') {
        $lonusersdir = $perlvar->{'lonUsersDir'};
        $londocroot = $perlvar->{'lonDocRoot'};
        $londaemons = $perlvar->{'lonDaemons'};
    }
    undef($perlvar);
}

my $lang = &Apache::lonlocal::choose_language();
&Apache::lonlocal::get_language_handle(undef,$lang);

if ($< != 0) {
    print &mt('You must be root in order to check Authoring Spaces.')."\n".
          &mt('Stopping')."\n";
    exit;
}

if ($lonusersdir eq '') {
    print &mt('Could not determine location of [_1] directory.',"'lonUsersDir'")."\n".
          &mt('Stopping')."\n";
    exit;
}

if ($londocroot eq '') {
    print &mt('Could not determine location of [_1] directory.',"'lonDocRoot'")."\n".
          &mt('Stopping')."\n";
    exit;
}

if ($londaemons eq '') {
    print &mt('Could not determine location of [_1] directory.',"'lonDaemons'")."\n".
          &mt('Stopping')."\n";
    exit;
}

# Abort if more than one argument.

my $parameter=$ARGV[0];
$parameter =~ s/^\s+//;
$parameter =~ s/\s+$//;

my (undef,undef,$uid,$gid) = getpwnam('www');

if ((@ARGV > 1) || (($parameter ne '') && ($parameter !~ /^(copy|undo)$/))) {
    print &mt('usage: [_1]','check_authoring_spaces.pl [copy|undo]')."\n\n".
          &mt('You should enter either no arguments, or just one argument -- either copy or undo.')."\n".
          &mt("copy - to copy files from Resources Space [_1] to Authoring Space [_2]",
              "'$londocroot/res/'","'$londocroot/priv/'")."\n".
          &mt('undo - to reverse those changes and restore overwritten files in Authoring Space back from: [_1] to [_2].',
              "'/home/httpd/overwritten","'$londocroot/priv'")."\n".
          &mt('no argument to do a dry run of the copy option, without actually copying anything.')."\n";
    exit;
}

print "\n".&mt("Comparing last modification date for files in published authors' Authoring Spaces with files in Resource Space.")."\n".
      "--------------------------------------------------------------------------------------------------------------\n\n".
      &mt('If run without an argument, the script will report what it would do when copying Resource Space files to Authoring Space, i.e., from [_1] to [_2], for which: (a) the last modification time for the file in /priv predates the last modification time for the corresponding file in /res, and (b) the contents of the files differ, and (c) the file is not a binary file.',
          "'$londocroot/res'","'$londocroot/priv/'")."\n\n";

my (undef,undef,$uid,$gid) = getpwnam('www');
my ($action) = ($parameter=~/^(copy|undo)$/);
if ($action eq '') {
    $action = 'dryrun';
}

if ($action eq 'dryrun') {
    print "\n\n".
          &mt('Running in exploratory mode ...')."\n\n".
          &mt('Run with argument [_1] to actually copy files from Resource Space ([_2]) to Authoring Space ([_3]), i.e., [_4]',
              "'copy'","'$londocroot/res'","'$londocroot/priv'","\n\nperl check_authoring_spaces.pl copy")."\n\n\n".
          &mt('Run with argument [_1] to restore previously overwritten Authoring Spaces back to [_2], i.e., [_3]',
              "'undo'","'$londocroot/priv'","\n\nperl check_authoring_spaces.pl undo")."\n\n\n".
          &mt('Continue? ~[y/N~] ');
    if (!&get_user_selection()) {
        exit;
    } else {
        print "\n";
    }
} else {
    print "\n *** ".&mt('Running in a mode where changes will be made.')." ***\n";
    if ($action eq 'copy') {
        print "\n".
              &mt('Mode is [_1] -- files will be copied to [_2].',
                  "'$action'","'$londocroot/priv'")."\n";
    } else {
        print "\n".
              &mt('Mode is [_1] -- files will be copied back to [_2].',
                  "'$action'","'$londocroot/priv'")."\n";
    }
    print &mt('Continue? ~[y/N~] ');
    if (!&get_user_selection()) {
        exit;
    } else {
        print "\n";
    }
}

my $logfh;
if ($action ne 'dryrun') {
    if (!open($logfh,">>$londaemons/logs/check_authoring_spaces.log")) {
        print &mt('Could not open log file: [_1] for writing.',
                  "'$londaemons/logs/check_authoring_spaces.log'")."\n".
              &mt('Stopping.')."\n";
              exit;
    } else {
        &start_logging($logfh,$action);
    }
}

# Authors hosted on this server
my %allauthors;
my %pubusers;
my @allskipped;

my @machinedoms;
my ($dir,$output);

if ($lonusersdir) {
    if (opendir($dir,$lonusersdir)) {
        my @contents = (grep(!/^\.{1,2}$/,readdir($dir)));
        closedir($dir);
        foreach my $item (@contents) {
            if (-d "$lonusersdir/$item") {
                if ($item =~ /^$match_domain$/) {
                    my $domain = $item;
                    unless (grep(/^\Q$domain\E$/,@machinedoms)) {
                        push(@machinedoms,$domain);
                    }
                }
            }
        }
    } else {
        $output = &mt('Could not open [_1].',"'$lonusersdir'")."\n";
        print $output;
        unless ($action eq 'dryrun') {
            &stop_logging($logfh,$output);
        }
        print &mt('Stopping')."\n";
        exit;
    }
}

if ($action eq 'undo') {
    my (%allcopied,@allskipped);
    if (-d "$londaemons/logs/checked_authoring_spaces") {
        if (opendir($dir,"$londaemons/logs/checked_authoring_spaces")) {
            my @contents = (grep(!/^\.{1,2}$/,readdir($dir)));
            closedir($dir);
            foreach my $dom (@contents) {
                if ((grep(/^\Q$dom\E/,@machinedoms)) && (-d "$londaemons/logs/checked_authoring_spaces/$dom")) {
                    my $domdir; 
                    if (opendir($domdir,"$londaemons/logs/checked_authoring_spaces/$dom")) {
                        my @unames = (grep(!/^\.{1,2}$/,readdir($domdir)));
                        closedir($domdir);
                        foreach my $uname (@unames) {
                            my %oldfiles;
                            my $skipped;
                            &descend_preserved_tree('',$londaemons,$dom,$uname,\%oldfiles);
                            print &mt('User: [_1], in domain: [_2] has [quant,_3,file].',$uname,$dom,scalar(keys(%oldfiles)))."\n".
                                  &mt('Continue? ~[y/N~] ');
                            if (!&get_user_selection()) {            
                                print &mt('Enter [_1] to skip this user.','1')."\n".
                                      &mt('Enter [_1] to stop.','2')."\n".
                                      &mt('Your input: ');
                                my $choice=<STDIN>;
                                chomp($choice);
                                $choice =~ s/^\s+//;
                                $choice =~ s/\s+$//;
                                if ($choice == 1) {
                                    my $output = &mt('Skipping user: [_1].',"'$uname'")."\n";
                                    print $output;
                                    print $logfh $output;
                                    push(@allskipped,$uname);
                                    next;
                                }
                                if ($choice == 2) {
                                    print &mt('Stopped.')."\n";
                                    my $output = &mt('Stopped at user: [_1].',"'$uname'")."\n";
                                    &stop_logging($logfh,$output);
                                    exit;
                                } else {
                                    print &mt('Invalid response:')." $choice\n";
                                    my $output = &mt('Skipping user: [_1].',"'$uname'")."\n";
                                    print $output;
                                    print $logfh $output;
                                    push(@allskipped,$uname);
                                    next;
                                }
                            }
                            foreach my $key (sort(keys(%oldfiles))) {
                                my $output;
                                unless ($key eq '') {
                                    my $source_path="$londaemons/logs/checked_authoring_spaces/$dom/$uname/$key";
                                    my $target_path="$londocroot/priv/$dom/$uname/$key";
                                    if (-e $source_path) {
                                        if (File::Copy::copy($source_path,$target_path)) {
                                            chown($uid,$gid,$target_path);
                                            system("touch -r $source_path $target_path");
                                            $output .= &mt('Copied [_1] to [_2].',
                                                           "'$source_path'","'$target_path'")."\n";
                                            push(@{$allcopied{$dom}{$uname}},$key);
                                            my $logfile;
                                            my $logname = $target_path.'.log';
                                            if (-e $logname) { 
                                                if (open($logfile,">>$logname")) {
                                                    print $logfile
"\n\n================= Retrieve ".localtime()." ================\n".
"Version: new\nSource: $source_path\nTarget: $target_path\n".
"Copied sucessfully.\n\n";
                                                    close($logfile);
                                                } else {
                                                    $output .= &mt('Could not open logfile [_1] to log retrieval.',$logname)."\n";
                                                }
                                            } else {
                                                $output .= &mt('Logfile [_1] does not exist.',$logname)."\n";
                                            }
                                        }
                                    } else {
                                        $output .= &mt('Source file [_1] does not exist.',$source_path)."\n";
                                    }
                                }
                                print $logfh $output;
                            }
                        }
                    }
                }
            }
        }
    } else {
        print &mt('Directory: [_1] does not exist',"$londaemons/logs/checked_authoring_spaces");
    }
    my ($copyinfo,$skipcount);
    if (keys(%allcopied) == 0) {
        $copyinfo = &mt('None')."\n";
    } else {
        foreach my $dom (sort(keys(%allcopied))) {
            if (ref($allcopied{$dom}) eq 'HASH') {
                $copyinfo .= "\n      ".&mt('Domain: [_1], number of authors: [_2]',
                                           "'$dom'",scalar(keys(%{$allcopied{$dom}})));
            }
        }
    }

    $skipcount = scalar(@allskipped);

    print "\n";
    my $output; 
    if ($skipcount) {
        $output = &mt('You skipped: [_1].',$skipcount)."\n".
                  join("\n",sort(@allskipped))."\n\n";
    }
    $output .= &mt('Copied back ... [_1]',$copyinfo)."\n";
    print $output;
    print "\n".&mt('Done.')."\n";
    print $logfh $output;
    &stop_logging($logfh);
    exit;
} elsif (($londocroot ne '') && (-d "$londocroot/res")) {
    if (-d "$londocroot/res") {
        my ($dir,$domdir);
        if (opendir($dir,"$londocroot/res")) {
            my @contents = (grep(!/^\.{1,2}$/,readdir($dir)));
            closedir($dir);
            foreach my $dom (@contents) {
                if ((grep(/^\Q$dom\E/,@machinedoms)) && (-d "$londocroot/res/$dom")) {
                    if (opendir($domdir,"$londocroot/res/$dom")) {
                        my @unames = (grep(!/^\.{1,2}$/,readdir($domdir)));
                        closedir($domdir);
                        foreach my $uname (@unames) {
                            if ($uname =~ /^$match_username$/) {
                                push(@{$pubusers{$uname}},$dom);
                            }
                        }
                    }
                }
            }
        }
    }

    my (%allcopied,$numcopied,$numchecked);

    # Iterate over directories in /home/httpd/html/res
    $numchecked = 0;
    foreach my $uname (sort(keys(%pubusers))) {
        if (ref($pubusers{$uname}) eq 'ARRAY') {
            foreach my $dom (@{$pubusers{$uname}}) {
                my %allfiles;
                &descend_res_tree('',$londocroot,$dom,$uname,\%allfiles,\$numchecked);
                if (keys(%allfiles))  { 
                    print &mt('User: [_1], in domain: [_2] has [quant,_3,file].',$uname,$dom,scalar(keys(%allfiles)))."\n".
                          &mt('Continue? ~[y/N~] ');
                    if (!&get_user_selection()) {
                        print &mt('Enter [_1] to skip this user.','1')."\n".
                              &mt('Enter [_1] to stop.','2')."\n".
                              &mt('Your input: ');
                        my $choice=<STDIN>;
                        chomp($choice);
                        $choice =~ s/^\s+//;
                        $choice =~ s/\s+$//;
                        if ($choice == 1) {
                            my $output = &mt('Skipping user: [_1].',"'$uname:$dom'")."\n";
                            print $output;
                            unless ($action eq 'dryrun') {
                                print $logfh $output;
                            }
                            push(@allskipped,"$uname:$dom");
                            next;
                        }
                        if ($choice == 2) {
                            print &mt('Stopped.')."\n";
                            my $output = &mt('Stopped at user: [_1].',"'$uname'")."\n";
                            &stop_logging($logfh,$output);
                            exit;
                        } else {
                            print &mt('Invalid response:')." $choice\n";
                            my $output = &mt('Skipping user: [_1].',"'$uname:$dom'")."\n";
                            print $output;
                            unless ($action eq 'dryrun') {
                                print $logfh $output;
                            }
                            push(@allskipped,$uname);
                            next;
                        }
                    }
                    foreach my $key (sort(keys(%allfiles))) {
                        if ($key ne '') {
                            my $source_path="$londocroot/res/$dom/$uname/$key";
                            my $target_path="$londocroot/priv/$dom/$uname/$key";
                            if ($action eq 'copy') {
                                my $output;
                                if (!-e "$londaemons/logs/checked_authoring_spaces") {
                                    mkdir("$londaemons/logs/checked_authoring_spaces",0755);
                                    chown($uid,$gid,"$londaemons/logs/checked_authoring_spaces");   
                                }
                                if (!-e "$londaemons/logs/checked_authoring_spaces/$dom") {
                                    mkdir("$londaemons/logs/checked_authoring_spaces/$dom",0755);
                                    chown($uid,$gid,"$londaemons/logs/checked_authoring_spaces/$dom");
                                }
                                if (!-e "$londaemons/logs/checked_authoring_spaces/$dom/$uname") {
                                    mkdir("$londaemons/logs/checked_authoring_spaces/$dom/$uname",0755);
                                    chown($uid,$gid,"$londaemons/logs/checked_authoring_spaces/$dom/$uname");
                                }
                                if (-e "$londaemons/logs/checked_authoring_spaces/$dom/$uname") {
                                    my $saveold_path = "$londaemons/logs/checked_authoring_spaces/$dom/$uname/$key"; 
                                    if ($key =~ m{/}) {
                                        my @subdirs = split(/\//,$key);
                                        my $file = pop(@subdirs);
                                        my $path = "$londaemons/logs/checked_authoring_spaces/$dom/$uname";
                                        while (@subdirs) {
                                            my $dir = pop(@subdirs);
                                            $path .= '/'.$dir;
                                            if (!-e $path) {
                                                mkdir($path,0755);
                                                chown($uid,$gid,$path);
                                            }
                                        }
                                    }
                                    if (-e $target_path) {
                                        if (File::Copy::copy($target_path,$saveold_path)) {
                                            chown($uid,$gid,$saveold_path);
                                            system("touch -r $target_path $saveold_path");
                                            $output .= &mt('Copied [_1] to [_2].',
                                                           "'$target_path'","'$saveold_path'")."\n"; 
                                            if (-e $source_path) {
                                                if (File::Copy::copy($source_path,$target_path)) {
                                                    chown($uid,$gid,$target_path);
                                                    system("touch -r $source_path $target_path");
                                                    $output .= &mt('Copied [_1] to [_2].',
                                                                   "'$source_path'","'$target_path'")."\n";
                                                    push(@{$allcopied{$dom}{$uname}},$key);
                                                    $numcopied ++;
                                                    my $logfile;
                                                    my $logname = $target_path.'.log';
                                                    if (-e $logname) {
                                                        if (open($logfile,">>$logname")) {
                                                            print $logfile
"\n\n================= Retrieve ".localtime()." ================\n".
"Version: new\nSource: $source_path\nTarget: $target_path\n".
"Copied sucessfully.\n\n";
                                                            close($logfile);
                                                        } else {
                                                            $output .= &mt('Could not open logfile [_1] to log retrieval.',$logname)."\n";
                                                        }
                                                    } else {
                                                        $output .= &mt('Logfile [_1] does not exist.',$logname)."\n";
                                                    }
                                                } else {
                                                    $output .= &mt('Failed to copy [_1] to [_2].',
                                                                   "'$source_path'","'$target_path'")."\n";
                                                }
                                            } else {
                                                $output .= &mt('Source file [_1] does not exist.',$source_path),"\n";
                                            }
                                        } else {
                                            $output .= &mt('Failed to copy [_1] to [_2].',
                                                           "'$target_path'","'$saveold_path'")."\n";
                                        }
                                    } else {
                                        $output .= &mt('Target file [_1] does not exist.',$target_path);
                                    }
                                } else {
                                    $output .= &mt('Directory needed to preserve pre-dated file from Authoring Space (prior to overwriting) not available.')."\n";
                                }
                                print $output;
                                print $logfh $output;
                            } elsif ($action eq 'dryrun') {
                                push(@{$allcopied{$dom}{$uname}},$key);
                                $numcopied ++;
                                print &mt('Would copy [_1] to [_2].',"'$source_path'","'$target_path'")."\n";
                            }
                        }
                    }
                }
            }
        }
    }

    my ($copyinfo,$skipcount);
    if (keys(%allcopied) == 0) {
        $copyinfo = &mt('None')."\n";
    } else {
        foreach my $dom (sort(keys(%allcopied))) {
            if (ref($allcopied{$dom}) eq 'HASH') {
                $copyinfo .= "\n      ".&mt('Domain: [_1], number of authors: [_2], number for copying: [_3], total number of files checked: [_4]',
                                           "'$dom'",scalar(keys(%{$allcopied{$dom}})),$numcopied,$numchecked);
            }
        }
    }

    $skipcount = scalar(@allskipped);

    print "\n";
    if ($action ne 'dryrun') {
        my $output = &mt('You skipped: [_1].',$skipcount)."\n".
                     join("\n",sort(@allskipped))."\n\n".
                     &mt('Copied ... [_1]',$copyinfo)."\n";
        print $output;
        print $logfh $output;
        &stop_logging($logfh);
    } else {
        if ($skipcount) {
            print &mt('You would have skipped: [_1].',$skipcount)."\n".
                  join("\n",sort(@allskipped))."\n\n";
        }
        print &mt('You would have copied ... [_1]',$copyinfo);
    }
    print "\n\n".&mt('Done.')."\n";
}

sub get_user_selection {
    my ($defaultrun) = @_;
    my $do_action = 0;
    my $choice = <STDIN>;
    chomp($choice);
    $choice =~ s/(^\s+|\s+$)//g;
    my $yes = &mt('y');
    if ($defaultrun) {
        if (($choice eq '') || ($choice =~ /^\Q$yes\E/i)) {
            $do_action = 1;
        }
    } else {
        if ($choice =~ /^\Q$yes\E/i) {
            $do_action = 1;
        }
    }
    return $do_action;
}

sub start_logging {
    my ($fh,$action) = @_;
    my $start = localtime(time);
    print $fh "*****************************************************\n".
              &mt('[_1] - mode is [_2].',
                  'check_authoring_spaces.pl',"'$action'")."\n".
              &mt('Started -- time: [_1]',$start)."\n".
              "*****************************************************\n\n";
    return;
}

sub stop_logging {
    my ($fh) = @_;
    my $end = localtime(time);
    print $fh "*****************************************************\n".
               &mt('Ended -- time: [_1]',$end)."\n".
              "*****************************************************\n\n\n";
    close($fh);
    return;
}

sub descend_res_tree {
    my ($dir,$londocroot,$dom,$uname,$allfiles,$numchecked) = @_;
    my $path = "$londocroot/res/$dom/$uname";
    if ($dir ne '') {
        $path .= "/$dir";
    }
    if (-d $path) {
        opendir(DIR,"$path");
        my @contents = grep(!/^\./,readdir(DIR));
        closedir(DIR);
        foreach my $item (@contents) {
            if (-d "$path/$item") {
                my $newdir;
                if ($dir eq '') {
                    $newdir = $item;
                } else {
                    $newdir = $dir.'/'.$item;
                }
                &descend_res_tree($newdir,$londocroot,$dom,$uname,$allfiles,$numchecked);
            } else {
                my $newpath;
                if ($dir eq '') {
                    $newpath = $item;
                } else {
                    $newpath = "$dir/$item";
                }
                if (-f "$londocroot/res/$dom/$uname/$newpath") {
                    next if ($item =~ /\.(tmp|subscription|meta)$/);
                    next if (-B "$londocroot/res/$dom/$uname/$newpath");
                    my $resfile = "$londocroot/res/$dom/$uname/$newpath";
                    my $cstrfile = "$londocroot/priv/$dom/$uname/$newpath";
                    if (-f $cstrfile) {
                        my $lastmodres = (stat($resfile))[9];
                        my $lastmodcstr = (stat($cstrfile))[9];
                        my $delta = $lastmodres - $lastmodcstr;
                        if (ref($numchecked)) {
                            $$numchecked ++;
                        }
                        if ($delta > 0) {
                            if (&File::Compare::compare($resfile,$cstrfile)) {
                                $allfiles->{$newpath} = $delta;
                            }
                        }
                    }
                }
            }
        }
    }
}

sub descend_preserved_tree {
    my ($dir,$londaemons,$dom,$uname,$allfiles) = @_;
    my $path = "$londaemons/logs/checked_authoring_spaces/$dom/$uname";
    if ($dir ne '') {
        $path .= "/$dir";
    }
    if (-d $path) {
        opendir(DIR,"$path");
        my @contents = grep(!/^\./,readdir(DIR));
        closedir(DIR);
        foreach my $item (@contents) {
            if (-d "$path/$item") {
                my $newdir;
                if ($dir eq '') {
                    $newdir = $item;
                } else {
                    $newdir = $dir.'/'.$item;
                }
                &descend_preserved_tree($newdir,$londaemons,$dom,$uname,$allfiles);
            } elsif (-f "$path/$item") {
                my $newpath;
                if ($dir eq '') {
                    $newpath = $item;
                } else {
                    $newpath = "$dir/$item";
                }
                $allfiles->{$newpath} = 1;
            }
        }
    }
}
