@echo off
@rem *************************************************************************
@rem This script supports coordinating the update of an Application source.
@rem
@rem First the user will update their applicacation and test it.  Then the user
@rem will distribute the resulting application source to each remote node.
@rem Finally, the RolloutService will coordinate actions on each node in order to
@rem shutdown the appropriate processes, apply the new patched application version
@rem into place, and eventually restart the processes with specific redeploy calls
@rem to trigger uptake of the app.
@rem
@rem The application source files will be preserved in the specified BACKUP_DIR,
@rem and then the patched application source will be moved into place to be picked
@rem up at the appropriate time.  If any part of this operation should fail, all
@rem steps will be reverted if possible.
@rem
@rem *************************************************************************
@rem
@rem Important internal variables:
@rem CURRENT      - This indicates the current application location
@rem  BACKUP_DIR   - This indicates the location where the CURRENT location
@rem                 should be moved to.  It could be later restored in the
@rem                 event of a rollback/revert.
@rem  PATCHED      - This indicates the location of the patched app to move into
@rem                 the CURRENT location
@rem  ACTION       - This value should be one of the following:
@rem                update
@rem                checkreq
@rem  RESUME       - This indicates validation should exit gracefully in the instance
@rem                 the operation may have already been performed
@rem
@rem  VERBOSE       - This value indicates extra output should be enabled
@rem
@rem *************************************************************************

SETLOCAL ENABLEEXTENSIONS ENABLEDELAYEDEXPANSION


SET SCRIPT_PATH=%~dp0
FOR %%i IN ("%SCRIPT_PATH%") DO SET SCRIPT_PATH=%%~fsi


:: Set the time zone string
for /f "tokens=2*"  %%a in ('reg query HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\TimeZoneInformation /v TimeZoneKeyName') do set TZ_STR=%%b
if DEFINED VERBOSE echo The timezone string is:  %TZ_STR%


GOTO :updateApp

::
::
::
:updateApp

 if NOT DEFINED ACTION (
   call :error "ACTION must be defined."
   GOTO:FINIS
 )

 set DOMAIN_DIR=%SCRIPT_PATH%\..\..\

 if "%ACTION%" == "update" (
   GOTO:updateCurrent
 ) else if "%ACTION%" == "checkreq" (
   GOTO:checkPrereq
 ) else (
   call :error "Fatal Error: %ACTION% is not supported."
   GOTO:FINIS
 )


::
::
::
:checkPrereq
 if DEFINED VERBOSE (
   call :info "Checking prerequisites"
 )

 call :validateArguments || GOTO:FINIS

 :: any other checks we can make here?

 call :info "checkreq Successful"
GOTO:done


::
::
::
:updateCurrent
 call :validateArguments || GOTO:FINIS


 if exist "%BACKUP_DIR%" (
     rd /S /Q %BACKUP_DIR%
     if ERRORLEVEL 1 (
       :: TODO: what can we do here?  move it out of the way?
       call :updateError "Could not remove %BACKUP_DIR%"
       GOTO:FINIS
     )
   )
 
 move /Y "!CURRENT!" "%BACKUP_DIR%" || (
   call :error "Failed to backup the current location: !CURRENT! to %BACKUP_DIR%"
   GOTO:FINIS
 )

 copy /Y "!PATCHED!" "!CURRENT!" || (
   call :error "Failed to move the patched app: !PATCHED! to the current location: !CURRENT!, will restore from %BACKUP_DIR%"
   move /Y "%BACKUP_DIR%" "!CURRENT!" || (
     call :error "Failed to restore the app at !CURRENT! from %BACKUP_DIR%"
   )
   GOTO:FINIS
 )
 ::TODO implement touch or make sure timestamp is updated?
 call :info "Successfully updated !CURRENT! with !PATCHED!"
GOTO:done


::
::
::
:validateArguments
 if DEFINED VERBOSE (
   call :info "Checking: CURRENT, PATCHED, BACKUP_DIR for valid values"
 )

 if NOT DEFINED CURRENT (
   call :error "Must suply CURRENT to update the current location"
   GOTO:FINIS
 )

 if NOT DEFINED PATCHED (
   call :error "Must supply PATCHED to update CURRENT"
   GOTO:FINIS
 )

 if DEFINED PATCHED (
   if not exist "!PATCHED!" (
     if DEFINED RESUME if DEFINED BACKUP_DIR if EXIST BACKUP_DIR (
       call :info "PATCHED does not exist after resume: $PATCHED.  It most likely has already been applied"
       GOTO:END
     )

     ::This is for relative paths     
     set REL_PATCHED=%DOMAIN_DIR%\!PATCHED!
     :: On Windows, a relative path can be found so we don't have to double check a the PATCHED relative to SCRIPT_PATH
     if not exist "!REL_PATCHED!" (
          call :error "Could not find the patched source relative to the domain or the domain/bin/patching dir: !PATCHED!.  It must be set to a valid archive or directory"
          GOTO:FINIS

     ) else (
        set PATCHED=!REL_PATCHED!
     )
   )
 )

 if NOT EXIST !CURRENT! (
   ::This is for relative paths     
   set REL_CURRENT=%DOMAIN_DIR%\!CURRENT!
     :: On Windows, a relative path can be found so we don't have to double check a the PATCHED relative to SCRIPT_PATH
   if not exist "!REL_CURRENT!" (
        call :error "Could not find the current source relative to the domain or the domain/bin/patching dir: !CURRENT!.  It must be set to a valid archive or directory"
        GOTO:FINIS
   ) else (
       set CURRENT=!REL_CURRENT!
   )
 )

   call :checkPathsUnique "!PATCHED!" "!CURRENT!" || (
     call :error "Application patchedLocation !PATCHED! is the same as the current location, !CURRENT!.  A unique patchedLocation path should be given in order to move the current location aside for preservation."
     GOTO:FINIS
   )


 if NOT DEFINED BACKUP_DIR (
   call :error "BACKUP_DIR must be defined"
   GOTO:FINIS
 )

 ::This is for relative paths
 for %%A in ("!BACKUP_DIR!") DO SET PARENT=%%~dpA
 call :info "Checking parent !PARENT! of backup dir !BACKUP_DIR!"
 if NOT EXIST !PARENT! (
   ::try the domain_dir
   set REL_BACKUP_DIR=%DOMAIN_DIR%\!BACKUP_DIR!
   for %%A in ("!REL_BACKUP_DIR!") DO SET PARENT_REL=%%~dpA
   if EXIST !PARENT_REL! (
     set BACKUP_DIR=!REL_BACKUP_DIR!
   ) else ( 
     call :error "Application backup dir !BACKUP_DIR! could not be located under !PARENT! or !PARENT_REL!"
   )
 )
 
 

 call :checkPathsUnique "!PATCHED!" "!BACKUP_DIR!" || (
   call :error "Application backupLocation !BACKUP_DIR! is the same location as the patchedLocation !PATCHED!.  A unique backupLocation path should be given in order to move the current location aside for preservation."
   GOTO:FINIS
 )

 call :checkPathsUnique "!CURRENT!" "!BACKUP_DIR!" || (
    call :error "Application backupLocation  !BACKUP_DIR! is the same as the current location, !CURRENT!.  A unique backupLocation path should be given in order to move the current location aside for preservation."
    GOTO:FINIS
  )

 :: TODO: can we properly resolve BACKUP_DIR if relative?

 if DEFINED VERBOSE (
   call :info "Verified CURRENT: !CURRENT!, PATCHED: !PATCHED!, BACKUP_DIR: %BACKUP_DIR% values"
 )
GOTO:EOF


::
:checkPathsUnique
 for %%A in ("%~1") DO for %%B in ("%~2") DO if "%%~fsA" == "%%~fsB" (
   exit /b 1
 ) else (
   exit /b 0
 )
GOTO:EOF


::
:: format a message to be used in string format
::
:getmsg
    set msgType=%1
    shift
    set msg="<%date% %time% %TZ_STR%> <%msgType%> <UpdateApplication> <%~1>"
GOTO:EOF

::
:: format a message and echo that message
::
:msg
    set msgType=%1
    shift
    set msg="<%date% %time% %TZ_STR%> <!msgType!> <UpdateApplication> <%~1>"
    echo !msg!
GOTO:EOF

::
:: format an INFO message and echo that message
::
:info
   call :msg INFO %1
GOTO:EOF

::
:: format a WARNING message and echo that message
::
:warning
echo **********  WARNING *********
:: TODO: and make it appear in log after validation
call :msg WARNING %1
echo **********  WARNING *********
GOTO:EOF

:error
   call :msg ERROR %1
GOTO:EOF


:debug
if NOT DEFINED DEBUG GOTO:EOF
echo **********  DEBUG  **********
echo %1 %2 %3 %4 %5
echo **********  DEBUG  **********
GOTO:EOF

:FINIS
ENDLOCAL
exit /b 1

:done
ENDLOCAL

ENDLOCAL
