#!/usr/bin/perl
# ----------------------------------------------------------------------------
# This software is Copyright (C) 2007, All Rights Reserved
# ----------------------------------------------------------------------------
# This program is protected by local and international copyright laws. Any
# use of this program is subject to the the terms of the license agreement
# included as part of this distribution archive. Any other uses are strictly
# prohibited without the written permission of the Vendor and all
# other rights are reserved.
# -----------------------------------------------------------------------------
# Warning : Modifying this program violates your license agreement!
# -----------------------------------------------------------------------------

package IT::Upload;

# load modules
use strict;

use IT::Globals;

# user defined globals (set by Init call)
use vars qw(@REQUIRED_USER_DEFINED_GLOBALS);
my @REQUIRED_USER_DEFINED_GLOBALS = qw(UPLOAD_DIR);

# library globals
use vars qw($INIT_CALLED $UPLOAD_DIR $EXPIRY_DATAFILE $EXPIRY_TIME_LIMIT $MAX_UPLOADS);
$INIT_CALLED                  = 0;    # indicated whether init() has been called or not
$UPLOAD_DIR                   = '';
$EXPIRY_DATAFILE              = 'unsavedUploadExpiryTimes.dat';
$EXPIRY_TIME_LIMIT         = 60*60*24; # 24 hours
$MAX_UPLOADS               = 10; # maximum uploads allowed in one form submission



# ----------------------------------------------------------------------------
# Function    : init
# Description : define filepaths and globals required by library
# Usage       : &init({
#                 'ARGUMENT' => "$value",
#               });
#
# Note: Don't call any functions that call &_die_unless_init_called()
#       from this function because then we'll get a confusing error about the init
#       function not being called instead of the actual error.
# ----------------------------------------------------------------------------

sub init {
  my $args = shift;
  if (!$args)                     { die("no arguments specified!"); }
  if (ref $args ne 'HASH')        { die("args must be passed by hash ref!"); }

  # error checking
  my($thisSubroutine) = (caller(0))[3];
  foreach (@REQUIRED_USER_DEFINED_GLOBALS) {
    unless (exists $args->{$_}) { &IT::Error::dieAsCaller("required $thisSubroutine argument '$_' not defined"); }
  }

  ## set user defined globals
  $UPLOAD_DIR      = $args->{'UPLOAD_DIR'};

  # error checking
  if ($UPLOAD_DIR eq '')                      { die "UPLOAD_DIR '$UPLOAD_DIR' isn't defined!"; }
  if (&IT::File::isRelativePath($UPLOAD_DIR)) { die "UPLOAD_DIR '$UPLOAD_DIR' can't be a relative path!"; }
  if (-e $UPLOAD_DIR && !-d $UPLOAD_DIR)      { die "UPLOAD_DIR '$UPLOAD_DIR' isn't a directory!"; }
  if ($UPLOAD_DIR && !-e $UPLOAD_DIR) {
    &IT::File::createDirPath($UPLOAD_DIR) || die "Couldn't create dir '$UPLOAD_DIR' $!";
  }

  # check filelock::init called
  &IT::FileLock::die_unless_init_called();

  # set sub globals
  $INIT_CALLED = 1;
}

# ----------------------------------------------------------------------------
# Function    : _die_unless_init_called()
# Description : Dies unless &init() has been called.  This is called
#               first by many public function to make sure the init function
#               for the module has been called.
# Usage       : &_die_unless_init_called();
# ----------------------------------------------------------------------------

sub _die_unless_init_called {
  if ($INIT_CALLED) { return; }

  my($thisPackage) = (caller(0))[0];
  my($subroutine) = (caller(1))[3];
  &IT::Error::dieAsCaller("You must call &${thisPackage}::init() before calling $subroutine",2);

}

# ------------------------------------------------------------------------------
# Function    : setExpiryCounter
# Description :
# Usage       : &IT::Upload::setExpiryCounter($filepath);
# ------------------------------------------------------------------------------

sub setExpiryCounter {
  my $filepath = shift || die "no filepath specified!";
  my $filepathFromUploadDir = &getFormattedPathFromUploadDir($filepath);

  # load expiry data
  my $filesWithExpiryTimes = &IT::Upload::_loadExpiryDataFromFile();

  # add file to expiry data
  $filesWithExpiryTimes->{$filepathFromUploadDir} = time() + $EXPIRY_TIME_LIMIT;

  # save expiry data
  &IT::Upload::_saveExpiryDataToFile($filesWithExpiryTimes);

}

# ------------------------------------------------------------------------------
# Function    : removeUploadExpiryCounter
# Description :
# Usage       : &IT::Upload::removeExpiryCounter($filepath);
# ------------------------------------------------------------------------------

sub removeExpiryCounter {
  my $filepath = shift || die "no filepath specified!";
  my $filepathFromUploadDir = &getFormattedPathFromUploadDir($filepath);

  # load expiry data
  my $filesWithExpiryTimes = &IT::Upload::_loadExpiryDataFromFile();

  # remove expiry entry
  delete $filesWithExpiryTimes->{$filepathFromUploadDir};

  # save expiry data
  &IT::Upload::_saveExpiryDataToFile($filesWithExpiryTimes);
}

# ------------------------------------------------------------------------------
# Function    : _loadExpiryDataFromFile
# Description :
# Usage       : my $filesWithExpiryTimes = &IT::Upload::_loadExpiryDataFromFile();
# ------------------------------------------------------------------------------

sub _loadExpiryDataFromFile {
  &_die_unless_init_called();

  # load hash from file
  my $datafile = "$UPLOAD_DIR/$EXPIRY_DATAFILE";
  my $filesWithExpiryTimes;
  if (-f $datafile) {
    &IT::FileLock::getLock('exclusive');
    $filesWithExpiryTimes = &IT::FreezeThaw::loadStructFromFile($datafile, 'ALLOW_PERL_EVAL');
    &IT::FileLock::releaseLock();
  }

  $filesWithExpiryTimes ||= {}; # default to hash if no file or empty file

  return $filesWithExpiryTimes;
}

# ------------------------------------------------------------------------------
# Function    : _saveExpiryDataToFile
# Description : save expiry data back to file.  Remove any files that have already
#               expired
# Usage       : &IT::Upload::_saveExpiryDataToFile($filesWithExpiryTimes);
# ------------------------------------------------------------------------------

sub _saveExpiryDataToFile {
  &_die_unless_init_called();
  my $filesWithExpiryTimes = shift || die "no \$filesWithExpiryTimes specified!";
  $filesWithExpiryTimes = {%$filesWithExpiryTimes}; # copy hash so we don't modify original below with delete

  # error checking
  unless (ref $filesWithExpiryTimes eq 'HASH') { die "\$filesWithExpiryTimes must be a hashref!"; }
  unless ($UPLOAD_DIR)                         { die "\$UPLOAD_DIR isn't defined!"; }

  # remove expired files
  foreach my $filepath (keys %$filesWithExpiryTimes) {
    my $expireTime = $filesWithExpiryTimes->{$filepath} || die "no expire time specified!";
    if ($expireTime <= time) {
      unlink "$UPLOAD_DIR/$filepath";
      delete $filesWithExpiryTimes->{$filepath};
    }
  }

  # save hash to file
  my $datafile = "$UPLOAD_DIR/$EXPIRY_DATAFILE";
  &IT::FileLock::getLock('exclusive');
  &IT::FreezeThaw::saveStructToFile($datafile, $filesWithExpiryTimes, 'perl');
  &IT::FileLock::releaseLock();
}

# ------------------------------------------------------------------------------
# Function    : getFormattedPathFromUploadDir
# Description : format expiry filepaths so they are all in the same format and
#               match when we look one up (even if the opposite slashes are used)
#               This can also be used to get the path from the upload dir for
#               converting a filepath to an url
# Usage       : my $formattedExpiryFilepath = &IT::Upload::getFormattedPathFromUploadDir($unformattedExpiryFilepath);
# ------------------------------------------------------------------------------

sub getFormattedPathFromUploadDir {
  my $fullFilepath = shift || die "no filename specified";
  my $uploadDir    = $SETTINGS->{'upload_dir'};

  # get filepath from uploaddir
  my $formattedUploadDir = &IT::File::cleanPath($uploadDir);
  my $formattedFilepath  = &IT::File::cleanPath($fullFilepath);
  my $pathFromUploadDir  = $formattedFilepath;
  $pathFromUploadDir =~ s|$formattedUploadDir/||g || die "Upload filepath '$formattedFilepath' doesn't contain uploadDir path '$formattedUploadDir'";

  return $pathFromUploadDir;
}

# ----------------------------------------------------------------------------
# end of file
1;

