#!/usr/bin/perl
###############################################################################
# getsub.pl   (GET SUBMITTED x) (only works on assignments)
# Chris Cera, chris@cera.ws
# Created :  1/31/2001
# Modified: everyday Last: 5/7/2001
# Version: 1.4
###############################################################################
# tested on WebCT version 3.1.3, probably incompatible with any other version
# you must run this program for each assignment, get its ID from the url first
# it will create a directory tree for each person's submission, if it exists
# you must have lynx on your system in this version, cURL could also be used
# this was developed on Linux, so I don't know if it will run from windows.
# there is no reason why it shouldn't be able to, except for the slash 
# issues that exist b/w windows and unices.  Hacking it would be minimal.
###############################################################################
# FEATURES
#
# Removes the <xmp> </xmp>
# Postprocessor flattens any combination of zip, tar, gz, uu and copies all
#     source code into a special source directory so it can be easily submitted
#     to moss.
# Postprocessor's main purpose is for submission of source code to a plagiarism
# detection system.
###############################################################################
# WISHLIST
#
# TGZ RECOGNITION
# getsub: tgz and queen arguments  
# oust lynx calls and replace with wget, this works on win32
# oust the few pipes and write to files for win32 compatibility.

# automatically change modification dates to match those submitted
# get this to work for quizzes
# MOD_DATE THING
# tar'ing in one line
# tgz support in postprocessor
# check for needed files before prompting for password
# sort downloaded output by section, take as vector or make lynx function
###############################################################################
# Linux localhost 2.2.18 #8 Tue Mar 27 05:24:01 EST 2001 i686 unknown
#
# Lynx Version 2.8.4dev.7 (03 Aug 2000)
# Built on linux-gnu Aug  4 2000 05:38:40
###############################################################################
# SunOS queen 5.7 Generic_106541-12 sun4u sparc SUNW,Ultra-4
#
# Lynx Version 2-4-2
# (c)1995 University of Kansas
# <lynx-help@ukanaix.cc.ukans.edu>
###############################################################################

###############################################################################
# DECLARE MODULES AND SUBROUTINES
###############################################################################

use Getopt::Std;
use File::Copy;
#use Term::ReadKey; 

sub mysystem;
sub myprint;
sub testandcreate;

###############################################################################
# CONSTANTS
###############################################################################

$PROGNAME     = "getsub";
$VERSION      = 1.4;
$DOMAIN       = "webct.drexel.edu";
$MAX_PASS_LEN = 32;                   # can't imagine a passwd longer than this
$NEW_PASSWAY  = 0;

# lower b/c we make the handles caps too
$strUnameFile = "./unames.txt";
$strSrcDir    = "src";

###############################################################################
# OPTS SECTION - check command line usage
###############################################################################

getopts ('u:p:c:b:xoevqh');

# this is the uniquely assigned id number for each assignment
#$idCourse  = "CS999";  # cs999 works too for some magical reason
#$idDropBox = "123456789";

# string options
$u          = $opt_u;
$p          = $opt_p;
$idCourse   = $opt_c;
$idDropBox  = $opt_b;

# booleans are capitalized, I just like it that way
$PRINT_VERSION   = $opt_v;
$PRINT_USAGE     = $opt_h;
$PERF_POSTP      = $opt_o;
if ($VERBOSE == 0) {$VERBOSE = $opt_e;}  # only overridable option
$SKIP_DLOAD      = $opt_x;
$ON_QUEEN        = $opt_q;

# print the options if verbose is set
myprint "VERBOSE MODE\n";
myprint "opt_u = $opt_u\n";
myprint "opt_p = $opt_p\n";
myprint "opt_c = $opt_c\n";
myprint "opt_b = $opt_b\n";
myprint "opt_x = $opt_x\n";
myprint "opt_o = $opt_o\n";
myprint "opt_e = $opt_e\n";
myprint "opt_v = $opt_v\n";
myprint "opt_q = $opt_q\n";

###############################################################################
# DONE opts section - DO NOT use opts below this line
###############################################################################



###############################################################################
# DO some validation of passed in arguments, and APPLY certain options
###############################################################################

#### EXIT RULES
if    ($PRINT_VERSION) {
    print STDOUT "$PROGNAME v$VERSION\n";
    exit (0);
}
elsif ($u         eq "" ||
       $idCourse  eq "" ||
       $idDropBox eq "" ||
       $PRINT_USAGE) {

    print STDOUT "1\$  $PROGNAME -u userid -c cname -b boxid  [-p passwd]  [-v]  [-oexq]\n";
    print STDOUT "2\$  $PROGNAME -h\n";
    print STDOUT "3\$  $PROGNAME -v\n";
    print STDOUT "\n";
    print STDOUT "        -p passwd (leave blank if you want to be prompted)\n";
    print STDOUT "        -c coursename\n";
    print STDOUT "        -b boxid (dropbox of the submission)\n";
    print STDOUT "        -x skip downloading step (to work:  dir structure must be right)\n";
    print STDOUT "        -o perform post processing step\n";
    print STDOUT "        -e verbose printing\n";
    print STDOUT "        -q must say when you are on queen\n";
    print STDOUT "        -v version info\n";
    print STDOUT "        -h help info (this message)\n";
    exit (1);
}

# PROMPT FOR PASSWORD IF NOT GIVEN
# password's are echo'ed at this point
if ($p eq "" && $opt_x == 0) {

    print STDOUT "password: (press CNTL-D (maybe twice) when finished, ^D is okay)\n";
    if ($NEW_PASSWAY) {
        ReadMode('noecho');
        $p = ReadLine(0);
    }
    else {
        system ("stty -echo");
        read (STDIN, $p, $MAX_PASS_LEN);
        system ("stty echo");
        chomp ($p);          # just in case they pressed enter once
        print STDOUT "\n";   # puts the echo'ed password on a line by itself
    }
}



if ($ON_QUEEN) {
    if ($PERF_POSTP) {
        print STDERR "Post-processing doesn't work right on queen yet\n";
    }
    $LYNXSOURCE = "lynx -source";
}
else {
    $LYNXSOURCE = "lynx --source";
}

###############################################################################
# PROGRAM BEGINS
###############################################################################

###############################################################################
# REAL WORK
#
#     input is simply a single column file of usernames
###############################################################################

if (! $SKIP_DLOAD) {

    testandcreate("$idDropBox");

    $urlSubmitList = "http://$DOMAIN/SCRIPT/$idCourse/scripts/student/dropbox_files_view.pl?MENU+_side_nav++$idDropBox++";

    open (input, "<$strUnameFile");

    if (-e $strUnameFile) {
        print STDERR "$PROGNAME will read in your list of usernames ...\n";
    }
    else {
        print STDERR "$strUnameFile is NOT FOUND\n";
        exit (1);
    }

    $i = 0;
    while ( <input> ) {
    
        # blasts newline
        chomp ($uname = $_);
    
        $unames[$i++] = $uname;
        $SRCFILE = "test/$_";
    
        print STDERR "checking -->$uname<-- .... \n";
    
        # the href we are grabbing is given by a relative path
        # do a source dump of that address, but first authenticate
    
        chomp ($data = `$LYNXSOURCE -auth=$u:$p $urlSubmitList$uname | grep HREF`);
    
        # output will look like
        # <A HREF="/SCRIPT/CS999/scripts/student/dropbox_files_view.pl?FILE_VIEW+_side_nav++123456789+asmt2.tar.gz+abrader" TARGET="right" onClick="doSelect('asmt2.tar.gz')"> asmt2.tar.gz</A>
        #      ^^^^^^^^^^^^
        #
        #    which has form
        #    <a href="....?FILE_VIEW+_side_nav++[$idDropBox]++[FILE]++[$uname]</a>
    
        #    ... so we'll pick up everything after first '>' and before next '<'
    
        $pos = 0;
        $switch = 1;
        while ( ($pos = index ($data,"HREF",$pos) ) > -1) {
    
            # check if first time in loop for this user
            if ($switch == 1) {

                # create directory if DNE
                testandcreate ("$idDropBox/$uname");

                $switch = 0;
            }
    
            $end_bracket = index ($data, ">", $pos);
            $stt_bracket = index ($data, "<", $end_bracket);
    
            # +2 is b/c there is always a space after the end bracket.
            # +1 at the end just works, and didn't care enough to find out why :D
            $len = ($stt_bracket - 1) - ($end_bracket + 2) + 1;
    
            # print STDERR "end=$end_bracket\nstt=$stt_bracket\npos=$pos\n";
            # print STDERR "len=$len\n";
    
            # rip out the filename from the url
            $filename = substr ($data, $end_bracket+2, $len);

            # now grab the filenames extension.
            @exten = split ('\.', $filename);
            $extnum = (scalar @exten) - 1; # 1st last ext
            $ex = $exten [$extnum];;
    
            print STDERR "    has submitted -->$filename<--\n";
    
            $urlSubmission = "$DOMAIN/SCRIPT/$idCourse/scripts/student/dropbox_files_view.pl?FILE_VIEW+_side_nav++$idDropBox+$filename+$uname";

            # word docs blow up on the *.xmp thing
            if ($ex eq "doc") {
                #print STDERR "GOT WORD DOC\n";
                mysystem ("$LYNXSOURCE -auth=$u:$p $urlSubmission > $idDropBox/$uname/$filename");
            }
            else {
                #print STDERR "NOT WORD DOC\n";
                mysystem ("$LYNXSOURCE -auth=$u:$p $urlSubmission | egrep -v \"<xmp>|</xmp>\"  > $idDropBox/$uname/$filename");
            }
    
            # might want to find a way use threads that way these calls could be
            # run in parallel.  That could be detrimental to webct though.  It
            # would be an interesting test.
    
            $pos = $end_bracket;
            $filename = "";
        }
        # if ($i >= 2) { exit (1); }
    }
    close (input);
}


###############################################################################
# post processing
###############################################################################
# flatten dirs, put all files in one directory
# handles most types
###############################################################################
# n^2 ????
#
# yes its n^2, sorry.  I'll make this more efficient eventually.  That doesn't
# mean the whole program sucks, it just means it may take a little bit longer.
# It does make the code much simpler when you separate these two components.
# You can run the post-processor w/o if you've already downloaded everything 
# without ever stepping into a single line of the downloading code.  In this
# case it runs in O(n) and the code is cleaner.
###############################################################################

if ($PERF_POSTP) {

    myprint "start post-processing, assumes dir structure is correct\n";

    open (input, "<$strUnameFile");

    if (-e $strUnameFile) {
        myprint "$PROGNAME will read in your list of usernames ...\n";
    }
    else {
        print STDERR "$strUnameFile is NOT FOUND\n";
        exit (1);
    }

    testandcreate ("$idDropBox.$strSrcDir/");

    $switch = 1;
    BIG: while ( <input> ) {
    
        # blasts newline
        chomp ($uname = $_);

        # check if it exists
        if (! -e "$idDropBox/$uname/") {
            next;
        }

        testandcreate ("$idDropBox.$strSrcDir/$uname");
    
        # the grep gets rid of . and ..
        
        opendir DROPBOX, "$idDropBox/$uname/" or die "weird error";
    	@allfiles = map "$idDropBox/$uname/$_", readdir DROPBOX;
    	#@allfiles = grep !/^$idDropBox\/$uname/\./, map "$idDropBox/$uname/$_", readdir DROPBOX;
    
        if (@allfiles == 0) {
            next;
        }


    	# allfiles is not prepended with absolute dir information
    	SMALL: while ($e = pop @allfiles) {

            myprint "pp:    $e\n";
            myprint "allfiles = @allfiles\n";

            #swap
            #$e is filename
            #$f is qualified filename

            @temp  = split ('/', $e); # just to get arg count

            if (! -d "$e") {
                $dir = "$temp[0]";
                for ($i = 1; $i <= (scalar @temp) - 2; $i++) {
                    $dir = "$dir/$temp[$i]";
                }
            }
            else {
                $dir = $e;
            }

            myprint "dir: $dir\n"; 

            $f = $e;
            $e = $temp [ (scalar @temp) - 1];

            if ($e eq "."  ||  $e eq "..") {
                next;
            }

            if    (-f $f) {
                # file
                ###########
                #determine type
                #if its an encoding, unencode and put back for later
                #else its a source textfile, finish it


                # this depends on file extensions
                @ext = split ('\.', $e);

                $last_ext_num = (scalar @ext) - 1; # 1st last ext
                $last_ext = $ext [$last_ext_num];;

                # create same as $f, filename w/o last extension
                $newguy = "$ext[0]";
                for ($i = 1; $i <= (scalar @ext) - 2; $i++) {
                    $newguy = "$newguy.$ext[$i]";
                }

                myprint "eee:  $e\n";
                myprint "new:  $newguy\n";

                ##########################
                # begin file type handling
                ##########################

                myprint ("It is a *.$last_ext file\n");

                ## UUENCODE
                if    ( $last_ext eq "uue" || 
                        $last_ext eq "UUE" ||
                        $last_ext eq "uu"  ||
                        $last_ext eq "UU"      ) {

                    mysystem ("uudecode -o $dir/$newguy $f");
                    mysystem ("rm $f");
                    # prepend at beginning
                    unshift @allfiles, "$dir/$newguy";
                    next SMALL;
                }

                ## GNU ZIP
                elsif ($last_ext eq "gz" || $last_ext eq "GZ") {
                    mysystem ("gunzip $f");
                    # gunzip deletes it for you by default

                    # prepend at beginning
                    unshift @allfiles, "$dir/$newguy";
                    next SMALL;
                }

                ## WINZIP
                elsif ($last_ext eq "zip" || $last_ext eq "ZIP") {
                    mysystem ("cd $dir && unzip $e");
                    mysystem ("rm $f");
                    redo BIG;
                }

                ## TAPE ARCHIVE
                elsif ($last_ext eq "tar" || $last_ext eq "TAR") {
                    mysystem ("cd $dir && tar xvf $e");
                    mysystem ("rm $f");
                    redo BIG;
                }

                elsif (  $last_ext eq "c"   || 
                         $last_ext eq "C"   ||
                         $last_ext eq "cpp" ||
                         $last_ext eq "CPP" ||
                         $last_ext eq "cc"  ||
                         $last_ext eq "CC"  ||
                         $last_ext eq "cxx" ||
                         $last_ext eq "CXX" ||
                         $last_ext eq "h"   ||
                         $last_ext eq "H"      ) {

                    myprint "$f: $last_ext is a source file, ADDING\n";
                    copy ("$f", "$idDropBox.$strSrcDir/$uname");
                    #mysystem ("g++ -E $f > $idDropBox.$strSrcDir/$uname/$e");
                }

                ## ELSE
                else {
                    # must not be encoded or source
                    #   its probably a doc, pdf
                    myprint "$f: $last_ext isn't encoded, IGNORING\n";
                }
            }
            elsif (-d $f) {
                # directory
                ###########
                #append it into @allfiles, and continue looping
    
                opendir NEW, "$f" or die "can't open $f";
                @newfiles = grep !/^\./, map "$dir/$_", readdir NEW;

                print STDERR "checking dir $f:  appending @newfiles";

                push @allfiles, @newfiles;
            }
            else {
                print STDERR "$f not a file or directory?\n";
                exit (1);
            }
    	}
    	closedir DROPBOX;
    }
    #print STDERR "EXECUTE: moss -d -l cc -c "Your comment here" */*.[Cc][Xx][Xx] */*.[Hh] */*.[Cc] */*.[Cc][Pp][Pp] */*.[Cc][Cc]\n";

    #print STDERR "EXECUTE: moss -d -l cc -c "Your comment here" $idDropBox.$strSrcDir/*/*.[Cc][Xx][Xx] $idDropBox.$strSrcDir/*/*.[Hh] $idDropBox.$strSrcDir/*/*.[Cc] $idDropBox.$strSrcDir/*/*.[Cc][Pp][Pp] $idDropBox.$strSrcDir/*/*.[Cc][Cc]\n";
}

###############################################################################
# SUBROUTINES
###############################################################################

sub mysystem {

    my ($arg) = @_;

    if ($VERBOSE) {
        print STDERR "$arg\n";
    }
    return system ("$arg");
}




# prints if at verbose depth
sub myprint {

    my ($arg, $depth) = @_;

    if ($depth eq "") {$depth = 1};
 
    if ($VERBOSE >= $depth) {
        print STDERR "$arg";
    }
}

sub testandcreate {

    my ($arg) = @_;

    if (-e "$arg") {
        myprint "$arg/ exists, not created.\n";
    }
    else {
        myprint "$arg/ doesn't exist, created.\n";
        mysystem ("mkdir $arg");
    }
}
