/* ApplicationEd
 * this program is intended to be called from the Suntan Special
 * Display Monitor.
 *
 * Description
 *   ApplicationEd is used by the Suntan Special Display Monitor to implement several
 *   features on the Application page. The argument is parsed by looking for space
 *   delimited words, beginning with a command, then appname, then within parentheses
 *   the status, and then the application parameter.
 *
 *   Syntax: ApplicationEd [appedarg [appname [(appstatus) apparg]]]
 *
 *   The appedarg can hanve the following values:
 *
 *   ApplicationEd('INIT') optionally check for updates and then populate the application listbox
 *   ApplicationEd('SELECT' AppName) fills the application text panel with a description of the application
 *   ApplicationEd('SEQEDCB' AppName) fills the Sequence Editor text panel with a description of the sequence
 *   ApplicationEd('INSTALL' AppName (AppStatus) AppArg) installs the application
 *   ApplicationEd('REFRESH') refreshes the application listbox with data from the Blonde Guy website
 *   ApplicationEd('EDIT') starts the Application settings editor
 *   ApplicationEd('UPDSEQ' SeqName) creates a sequence of apps to be updated.
 *
 * Copyright (C) 2018 Blonde Guy
 * All Rights Reserved
 */

signal on Syntax

originalDir = Directory()
cr = d2c(13)

call InitGlobals

call Directory suntanDir
call 'cd-env'
Applicationline = arg(1)
parse var Applicationline Applicationcmd Applicationapp '(' Applicationstatus ')' Applicationparm
Applicationapp = strip(Applicationapp)
Applicationparm = strip(Applicationparm)

/* handy debugging line
call LogCdi "ApplicationEd(" || Applicationcmd || "," Applicationapp || "," Applicationstatus || "," Applicationparm || ")"
  */
 
select
   when Applicationcmd = 'INIT' then do
      call SysSleep 1
      call CheckContext
      call SetAppBits
      call LogCdi "Suntan Special version 0.69 running in" cdglobal("suntanDir")
      if cdglobal('AutoCheckUpdates') = 1 then do
         call LogCdi '!apped SStatus: Downloading'
         call getUpdate
      end
      call RefreshList
   end
   when Applicationcmd = 'SELECT' then do
      cf = getcf(Applicationapp)
      appdesc = FindDetails(cf)
      call LogCdi '!apped D' || appdesc
   end
   when Applicationcmd = 'SEQEDCB' then do
      parse var ApplicationLine Applicationcmd Applicationapp Applicationarg
      /* call LogCdi "ApplicationEd(" || Applicationcmd || "," Applicationapp || "," Applicationparm || ")" */
      Applicationstatus = ''
      if translate(Applicationapp) = "EDITSEQUENCE" then do
         call LogCdi "!tpcallback" "Edit the" Applicationarg "sequence."
      end
      else if translate(Applicationapp) = "EXECUTESEQUENCE" then do
         call LogCdi "!tpcallback" "Execute the" Applicationarg "sequence."
      end
      else if translate(Applicationapp) = "SET" then do
         parse var Applicationarg setKeyword '=' setValue
         call LogCdi "!tpcallback" "Set the value of" strip(setKeyword) "to" strip(setValue)
      end
      else if left(Applicationapp, 1) = ';' then do
         call LogCdi "!tpcallback" "Comment:" substr(Applicationapp, 2) applicationarg
      end
      else do
         cf = getcf(Applicationapp)
         appdesc = FindDetails(cf)
         if Applicationarg <> '' then do
            appdesc = "Application Arg:" Applicationarg || cr || appdesc
         end
         call LogCdi "!tpcallback" appdesc
      end
   end
   when Applicationcmd = 'INSTALL' then do
      call Directory originalDir
      skip = 0
      apprc = 1
      do while skip = 0
         cf = getcf(Applicationapp)
         if cf <> '' then do
            if ApplicationStatus = 'Batch Mode' then do
               call LogCdi '!apped DBatchMode...' || cr || "calling:" cf
            end
            else do
               appdesc = FindDetails(cf)
               call LogCdi '!apped D' || appdesc
            end
            skip = 1
         end
         else do
            message = "Suntan Special has encountered an error." || cr || cr || "The application" Applicationapp,
                      "was not found. The source path is" cdglobal('sourceDir') || ". The current directory is" Directory() ||,
                       cr || cr || "Please check your media and press OK to",
                      "try to process" Applicationapp "again." || cr || cr || "Press cancel to",
                      "skip the installation."
            response = messagedialog('Error', 5, message)
            if response = 'Cancel' then do
               skip = 1
            end
            if stream(pauseFile, 'c', 'query exists') \= '' then do
               skip = 1
            end
         end
      end
      if cf = '' then do
         call LogCdi Applicationapp 'was not found.'
      end
      else do
         response = 'OK'
         if cdglobal('batchmode') = 'SingleStep' then do
            response = makeInstallPage( Applicationapp, Applicationparm, cf )
         end
         status = 'OK'
         do while status = 'OK'
            if right(translate(cf), 7) = 'CDI.CMD' then do
               call LogCdi '!apped SStatus: Installing'
               apppath = left(cf, length(cf) - 8)
               apprc = ExecuteCommand(apppath, Applicationparm)
               if apprc = 0 & Applicationstatus <> '' then do
                  if ApplicationStatus <> 'Configuration' then do
                     call LogCdi '!apped R' || appname '(' || Applicationstatus || ')' || d2c(10) || appname '(Up to Date)'
                     Applicationstatus = 'Up to Date'
                  end
                  status = 'Done'
               end
            end
            else do
               apprc = WebInstall(Applicationapp, Applicationparm)
            end
            if apprc <> 0 then do
               status = MessageDialog("Error", 5, "The command" appname,
                                      "did not execute successfully." || cr || cr || "Press OK",
                                      "to repeat the command." || cr || "Press Cancel to",
                                      "skip the command.")
            end
         end
      end
      call LogCdi '!apped SStatus: Ready'
      call directory originalDir
      return apprc
   end
   when Applicationcmd = 'REFRESH' then do
      call LogCdi '!apped SStatus: Downloading'
      if getUpdate() = 0 then do
         call RefreshList
         call CheckSuntan
      end
      else do
         call LogCdi "The application list was not refreshed."
         call RefreshList
         call LogCdi '!apped SStatus: Ready'
      end
   end
   when Applicationcmd = 'EDIT' then do
      call SetEd
   end
   when Applicationcmd = 'UPDSEQ' then do
      call LogCdi '!apped SStatus: Creating Update Sequence' Applicationapp
      call CreateUpdateSequence
   end
   otherwise do
      call LogCdi "ApplicationEd Error:" arg(1)
   end
end

call Directory originalDir
return

/* refreshList - fill Application page listbox with apps */
refreshList:

call LogCdi '!apped SStatus: Refreshing'
call enumerateApps
call enumerateWeb
call LogCdi '!apped SStatus: Scanning DB'
call enumerateDB
call LogCdi '!apped SStatus: Merging Local and Web'
call mergelocalweb
call LogCdi '!apped SStatus: Preparing App List'
call makeList
call LogCdi '!apped SStatus: Ready'

return

/* SetAppBits - choosing which applications to display */
SetAppBits:

call LogCdi '!apped B' || cdglobal('showLocalApps') || cdglobal('showWebApps') || cdglobal('showUpdateAvailable') || cdglobal('showNotInstalled') || cdglobal('showUptoDate')

return

/* CreateUpdateSequence - create sequence of apps that need updating
 * this routine takes one argument, the sequence filename
 */
CreateUpdateSequence:

if Applicationapp = '' then do
   updseqname = suntanDir || '\update.seq'
end
else do
   updseqname = suntanDir || '\' || Applicationapp
end
ApplicationApp = ''

/* create list of apps */
call enumerateApps
call enumerateWeb
call enumerateDB
call mergelocalweb

call SysFileDelete updseqname

call lineout updseqname, '; updates available' date()

do i=1 to appList.0
   if appListStatus.i = 'Update Available' then do
      call lineout updseqname, appListName.i      
   end
end

do while Applicationapp <> ''
  parse var Applicationapp thisApp ';' Applicationapp
  if thisApp <> '' then do
     call lineout updseqname, thisApp
  end
end

call lineout updseqname

call LogCdi '!apped SStatus: Ready'

return

/* enumerateApps
 *
 * creates appLocal.*, appLocalTime.* and appLocalName
 */
enumerateApps:

if cdglobal('showLocalApps') = 0 then do
   appLocal.0 = 0
   appLocalTime.0 = 0
   appLocalName.0 = 0
   return
end

/* parse source path into source directories */
sourcePath = cdglobal('sourceDir')
j = 0
do while sourcePath \= ''
   parse var sourcePath sourceDir ';' sourcePath
   if sourceDir \= '' then do
      j = j + 1
      Dir.j = sourceDir
   end
end
Dir.0 = j

/* scan each source directory for application directories */
AppNo = 0
do j = 1 to Dir.0
   sourceDir = Dir.j
   call SysFileTree sourceDir || '\*.*', 'tempList', 'DO'
   do k = 1 to tempList.0
      cdi = stream(tempList.k || '\cdi.cmd', 'c', 'query exists')
      if cdi <> '' then do
         AppNo = AppNo + 1
         AppList.AppNo = TempList.k         
      end
   end
end

AppList.0 = AppNo

napp = 0

call BubbleSort

/* scan all apps, skipping duplicates */
do j=1 to AppList.0
   key = translate(substr(AppList.j, lastpos('\', AppList.j) + 1))
   if key <> prev then do
      app = substr(AppList.j, lastpos('\', AppList.j) + 1)
      napp = napp + 1
      AppLocal.napp = AppList.j
      AppLocalName.napp = app
      appLocalCat.napp = getCdiVar('productCategory' AppList.j || '\cdi.cmd')

      /* calculate the date of the application */
      adate = GetProductDate(appList.j || '\cdi.cmd')
      parse var adate ayear '-' t2 '-' t3 t4 ':' t5 ':' t6
      t1 = right(ayear, 2)
      appLocalTime.napp = strip(t1 || t2 || t3 || t4 || t5)

      /* Debugging aid
      if translate(appLocalName.napp) = 'ANAHCI' then do
         call LogCdi "appLocal." || napp "=" appLocal.napp
         call LogCdi "appLocalName." || napp "=" appLocalName.napp
         call LogCdi "appLocalTime." || napp "=" appLocalTime.napp
      end
      */
   end
   prev = key
end
appLocal.0 = napp
appLocalTime.0 = napp
appLocalName.0 = napp

return


/* enumerateWeb
 * puts list of web apps into appWeb.*, appWebTime.* and appWebName.*
 */
enumerateWeb:

if cdglobal('showWebApps') = 0 then do
   appWeb.0 = 0
   appWebTime.0 = 0
   appWebName.0 = 0
   return
end

updateDir = cdglobal('suntanDir') || '\update'

/* get list of available updates */
rc = SysFileTree(updateDir || '\*.*', 'appWeb.', 'FT')
if rc <> 0 then do
   call LogCdi "Not enough memory to complete updates."
   return 1
end
if appWeb.0 = 0 then do
   return 0
end
do i=1 to appWeb.0
   parse var appWeb.i time size flags webName
   webName = strip(webName)
   time = GetProductDate(webName)
   parse var time t1 '-' t2 '-' t3 t4 ':' t5 ':' t6
   appWebTime.i = strip(right(t1, 2) || t2 || t3 || t4 || t5)
   appWebName.i = filespec('Name', webName)
   appWebCat.i = getCdiVar('productCategory' webName)
end
appWebTime.0 = appWeb.0
appWebName.0 = appWeb.0

return


/* get list of installed applications
 *
 * produces dbaList.*, dbaTime.* and dbaLine.*
 */
enumerateDB:

dbfile = suntanDir || '\appdb.txt'
dbaList.0 = 0
dbaTime.0 = 0
dbaLine.0 = 0
dbcount = 0
do while lines(dbfile) > 0
   dbline = linein(dbfile)
   dbcount = dbcount + 1
   parse var dbline installDate ',' appDir ',' appArg ',' retcode ',' installTime
   if (lastpos('\', appDir) > 0) & (retcode = 0) then do
      appName = translate(strip(substr(appDir, lastpos('\', appDir) + 1)))
      installTime = strip(installTime)
      appTime = substr(installTime, 3, 2) ||,  /* year */
                substr(installTime, 6, 2) ||,  /* month */
                substr(installTime, 9, 2) ||,  /* day */
                substr(installTime, 12, 2) ||, /* hour */
                substr(installTime, 15, 2) /* ||, minute */
                /* substr(installTime, 18, 2)     second */
      match = 0
      do i=1 to dbaList.0
         if appName = dbaList.i then do
            dbaTime.i = appTime
            dbaLine.i = dbline
            match = 1
         end
      end
      if match = 0 then do
         i = dbaList.0 + 1         
         dbaList.i = appName
         dbaTime.i = appTime
         dbaLine.i = dbline
         dbaList.0 = i
         dbaTime.0 = i
         dbaLine.0 = i
      end
   end
end
call stream dbfile, 'c', 'close'

if dbcount > dbaList.0 then do
   dbbackup = suntanDir || '\appdb_backup.txt'
   address cmd '@copy' dbfile dbbackup '> nul'
   call SysFileDelete dbfile
   do i=1 to dbaLine.0
      call lineout dbfile, dbaLine.i
   end
   call stream dbfile, 'c', 'close'
end

return

/* makelist */
makelist:

/* create command string */

x = 'L'
do i=1 to appList.0

   if (showUptoDate & appListStatus.i = 'Up to Date') |,
      (showUpdateAvailable & appListStatus.i = 'Update Available') |,
      (showNotInstalled & appListStatus.i = 'Not Installed') |,
      (left(appListStatus.i, 3) = 'APP') |,
      (left(appListStatus.i, 3) = 'DBA') |,
      (appListStatus.i = 'Operation') |,
      (appListStatus.i = 'Configuration') then do

      if x <> 'L' then do
         x = x || d2c(10)
      end
      x = x || appListName.i '(' || appListStatus.i || ')'
      
   end
end

/* debugging aid
call LogCdi "there are" appList.0 "items for the list with length" length(x) || "."
call LogCdi "!apped SStatus: drawing"
*/
call LogCdi "!apped" x

call Directory originalDir
return 0


/* merge local and web apps */
mergelocalweb:

do i=1 to appLocal.0
   appList.i = appLocal.i
   appListTime.i = appLocalTime.i
   appListName.i = appLocalName.i
   appListCat.i = appLocalCat.i
end
appList.0 = appLocal.0
appListTime.0 = appLocalTime.0
appListName.0 = appLocalName.0
appListCat.0 = appLocal.0

do j=1 to appWeb.0
   match = 0
   do i=1 to appList.0
      /* case insensitive match */
      if translate(appWebName.j) = translate(appListName.i) then do
         if appWebTime.j - appListTime.i > appTimeMargin then do
            appList.i = appWeb.j
            appListTime.i = appWebTime.j
            appListName.i = appWebName.j
            appListCat.i = appWebCat.j
         end
         match = 1
      end
   end

   if match = 0 then do
      i = appList.0 + 1
      appList.i = appWeb.j
      appListTime.i = appWebTime.j
      appListName.i = appWebName.j
      appListCat.i = appWebCat.j
      appList.0 = i
      appListTime.0 = i
      appListName.0 = i
      appListCat.0 = i
   end   
end

do i=1 to appList.0
   appListStatus.i = 'Not Installed'
   do j=1 to dbaList.0
      if dbaList.j = translate(appListName.i) then do
         if datatype(appListTime.i) <> 'NUM' then do
            appListStatus.i = 'APP' appListTime.i
         end
         else if datatype(dbaTime.j) <> 'NUM' then do
            appListStatus.i = 'DBA' dbaTime.j
         end
         else if appListTime.i - dbaTime.j > appTimeMargin then do
            appListStatus.i = 'Update Available'
            
            /* Debugging aid
            if appListName.i = 'anAHCI' then do
               call LogCdi "anAHCI List:" appListTime.i "dba:" dbaTime.j "(" || dbaList.j || ")"
            end
            */
         end
         else do
            appListStatus.i = 'Up to Date'
         end
      end
   end
   if (appListCat.i = 'Operation') | (appListCat.i = 'Configuration') then do
      appListStatus.i = appListCat.i
   end
end

return

/* initialize global variables from Suntan Special settings */
InitGlobals:

Call RxFuncAdd 'SysLoadFuncs', 'RexxUtil', 'SysLoadFuncs'
Call SysLoadFuncs

phoneUserid = cdglobal('phoneUser')
if phoneUserid = '' then do
   phoneUserid = QuerySettings('phoneUser', 'Query')
   if phoneUserid = '' then do
      call LogCdi "No user id given"
      return 1
   end
   else do
      call SetSetting 'phoneUser', phoneUserid
   end
end

phonePassword = cdglobal('phonePassword')
if phonePassword = '' then do
   phonePassword = QuerySettings('phonePassword', 'Query')
   if phonePassword = '' then do
      call LogCdi "No password given"
      return 1
   end
   else do
      call SetSetting 'phonePassword', phonePassword
   end
end

phoneHomeURL = cdglobal('phoneHomeURL')
if phoneHomeURL = '' then do
   phoneHomeURL = QuerySettings('phoneHomeURL', 'Query')
   if phonePassword = '' then do
      call LogCdi "No URL given"
      return 1
   end
   else do
      call SetSetting 'phoneHomeURL', phoneHomeURL
   end
end

setupDir = cdglobal(setupDir)
if setupDir = '' then do
   setupDir = QuerySettings('setupDir', 'Query')
   if phonePassword = '' then do
      call LogCdi "No setup directory given"
      return 1
   end
   else do
      call SetSetting 'setupDir', setupDir
   end
end

suntanDir = cdglobal('suntanDir')
tempDir = cdglobal('tempDir')
rc = SuntanMkDir(tempDir)
updateDir = suntanDir || '\update'

showNotInstalled = cdglobal('showNotInstalled')
showUpdateAvailable = cdglobal('showUpdateAvailable')
showUptoDate = cdglobal('showUptoDate')

/* applications must be this much newer (minutes) */
appTimeMargin = 60 * 12

month.1 = 'Jan'
month.2 = 'Feb'
month.3 = 'Mar'
month.4 = 'Apr'
month.5 = 'May'
month.6 = 'Jun'
month.7 = 'Jul'
month.8 = 'Aug'
month.9 = 'Sep'
month.10= 'Oct'
month.11= 'Nov'
month.12= 'Dec'

/* Mirror List */
hobbesURL2 = 'https://us01.hobbesarchive.com/pub'
hobbesURL3 = 'http://ftpmirror1.infania.net/pub/Hobbes/'
hobbesURL = 'http://hobbesarchive.com/Home/Download?path=/Hobbes/pub'

return

/* download list of updates */
getUpdate:

localFile = tempDir || '\update.zip'
result = GetRemoteFile(phoneHomeURL, phoneUserid, phonePassword, localFile)
if result = 1 then do
   call LogCdi "unable to get list of updates"
   return 1
end

address cmd '@deltree /y' updateDir '> nul'
if rc <> 0 then do
   call LogCdi "Error" rc "erasing previous update directory" updateDir
end
call SuntanMkDir updateDir
call SysFileTree updateDir || '\*.*', 'oldup.', 'FO'
do i=1 to oldup.0
   call SysFileDelete oldup.i
end
drop oldup
address cmd '@unzip -o -qq' localFile '-d' updateDir
call SysFileDelete localFile
if rc <> 0 then do
   call LogCdi "Error" rc "unzipping update file"
   call LogCdi "localFile =" localFile
   call LogCdi "updateDir =" updateDir
   return 1
end

call Directory originalDir
call LogCdi "The application list has been downloaded."
return 0


/* returns 0 for success
           1 for failure
*/
GetRemoteFile:

url = arg(1)
userid = arg(2)
password = arg(3)
localFile = arg(4)

localDir = strip(filespec('Drive', localFile) || filespec('Path', localFile), 'Trailing', '\')
call SysMkDir localDir

/* After cruzio moved the server, I changed URL. old apps still have old url */
if pos('ftp://ftp.blondeguy.com/hidden/SuntanV6', url) = 1 then do
   call LogCdi "url shortened"
   call LogCdi "before:" url
   url = 'ftp://ftp.blondeguy.com/SuntanV6/' || substr(url, 40)
   call LogCdi "after:" url
end

if tcpip32() = 0 then do
   call LogCdi "Using FTPremoteFile to fetch" url
   return FTPremoteFile(url, userid, password, localFile)
end

pwstring = ''
if pos('ftp://ftp.blondeguy.com/SuntanV6', url) then do
   pwstring = '--user=' || userid '--password=' || password
end

call Directory localDir
call SysFileDelete localFile
errFile = suntanDir || '\cd-error.txt'
zFile = substr(url, lastpos('/', url) + 1)
zTest = pos('F=', zFile)
if zTest > 0 then do
   zFile = substr(zFile, lastpos('F=', zFile) + 2)
end
wgetua = '--user-agent="Mozilla/5.0 (OS/2; Warp 4.5; rv:10.0.5) Gecko/20100101 Firefox/10.0.5"'
call LogCdi "Trying" SysSearchPath('Path', 'wget.exe') "to get" url
address cmd '@wget --tries=1 --no-check-certificate --output-document=' || zFile pwstring wgetua '--output-file=' || errFile '"' || url || '"'
wrc = rc
if wrc = 0 then call LogCdi "retrieved" zFile
else do
   do while lines(errFile)
      lline = linein(errFile)
      if pos('..........', lline) = 0 then do
         call LogCdi 'log>>' lline
      end
   end
   call stream errFile, 'c', 'close'
   call LogCdi "Error" wrc "retrieving" zFile
   call Directory originalDir
   return 1
end
call SysFileDelete errFile
if stream(zFile, 'c', 'query exists') = '' then do
   call LogCdi "file not found:" zFile
   call LogCdi "wget failed to retrieve" url
   call Directory originalDir
   return 1
end
outFile = filespec('Name', localFile)
if zFile <> outFile then do
   address cmd '@copy' zFile outFile '> nul'
   if rc <> 0 then do
      call LogCdi "Error" rc "copying" zFile "to" outFile
   end
   call SysFileDelete zFile
end
call Directory originalDir
return 0


GetAppURL:

appfile = updateDir || '\' || arg(1)
AppURL = ''

do while lines(appfile) & AppURL = ''
   appline = linein(appfile)
   pp = pos('AppURL', appline)
   ep = pos('=', appline)
   if pp > 0 & pp < ep then do
      parse var appline '"' appURL '"'
   end
end
call stream appfile, 'c', 'close'

return AppURL


WebInstall:

appname = arg(1)
apprc = 1

call LogCdi '!apped SStatus: Downloading'
      
appURL = GetAppURL(appname)
if appURL <> '' then do
   parse var appURL partURL ';' appURL
   call LogCdi "Downloading files for" appname
   result = GetRemoteFile(partURL, phoneUserid, phonePassword, tempDir || '\app.zip')
   if result <> 0 then do
      call LogCdi "unable to retrieve update for" appname
   end
   else do
      if Directory(setupDir) = '' then do
         rc = SuntanMkDir(setupDir)
         if rc <> 0 then do
            call LogCdi "Error" rc "creating" setupDir
         end
      end
      if SysRmDir(appname) <> 3 then do
         address cmd '@deltree /y' appname '> nul'
      end
      call Directory originalDir
      partError = 0
      partErrorMsg = "Installation error" || cr || "Suntan Special",
                     "was unable to install" appname "because"
      address cmd '@unzip -qq -o' tempDir || '\app.zip' '-d' setupDir || '\' || appname
      if rc <> 0 then do
         call LogCdi "Error" rc "unzipping" appname "to" setupDir
         partErrorMsg = partErrorMsg "error" rc "was encountered unzipping",
                        "the application,"
         partError = 1
      end
      call SysFileDelete tempDir || '\app.zip'
      call Directory setupDir
      do while appURL <> ''
         parse var appURL partURL ';' appURL
         appURL = strip(appURL,,"'")
         appURL = strip(appURL,,'"')
         partURL = strip(partURL)
         partURL = strip(partURL,,'|')
         zTest = pos('F=', partURL)
         if zTest > 0 then do
            localFile = setupDir || '\' || appname || '\' || substr(partURL, lastpos('F=', partURL) + 2)
         end
         else do
            localFile = setupDir || '\' || appname || '\' || substr(partURL, lastpos('/', partURL) + 1)
         end
         
         mirrorURL = partURL
         do while mirrorURL <> ''
            result = GetRemoteFile(mirrorURL, phoneUserid, phonePassword, localFile)
            if result <> 0 then do
               lastURL = mirrorURL
               mirrorURL = GetNextMirror(mirrorURL)
               if mirrorURL <> '' then do
                  call LogCdi "Error" result "getting" lastURL
                  call LogCdi "Trying" mirrorURL
               end
            end
            else do
               mirrorURL = ''
            end
         end

         if result <> 0 then do
            /* query the user for missing part */
            dialogName = "Missing File"
            partUser = filespec("Name", localFile)
            message = "Suntan Special is unable to retrieve the file from",
                      partURL "This file is required to install" appName || '.' || cr || cr,
                      "Please obtain this file and enter its location." || cr || cr,
                      "If the file cannot be located, clear the entry field,",
                      "and enter an empty file to cancel the installation."
            response = query(dialogName, partUser, message, "File")
            if response = '' then do
               call LogCdi "unable to retrieve file" partURL
               partErrorMsg = partErrorMsg "the file" partURL "could not be retrieved."
               partError = 1
            end
            else do
               if response <> localFile then do
                  call LogCdi "copying" response "to" localFile
                  address cmd '@copy "' || response || '"' localFile '> nul'
                  if rc <> 0 then do
                     call LogCdi "Error" rc "copying" response "to" localFile
                     partError = 1
                  end
               end
            end
         end
      end
      call Directory originalDir
      if partError = 0 then do
         call LogCdi '!apped SStatus: Installing'
         apprc = ExecuteCommand(SetupDir || '\' || appname, arg(2))
         if apprc = 0 & Applicationstatus <> '' then do
            if ApplicationStatus <> 'Configuration' then do
               call LogCdi '!apped R' || appname "(" || Applicationstatus || ")" || d2c(10) || appname "(Up to Date)"
               Applicationstatus = 'Up to Date'
            end
            Status = 'Done'
         end
      end
      else do
         call LogCdi  "Error:" translate(partErrorMsg, '-', cr) "so the installation was abandoned."
         call MessageDialog "Error", 1, partErrorMsg "so the installation was abandoned."
      end
      if cdglobal('saveWebApps') = 0 | partError = 1 then do
         if DeleteDownloadedApp() <> 0 then do
            call LogCdi "Error" rc "deleting downloaded application" appname
         end
      end
      else do
         call LogCdi "downloaded application" appname "has been saved."
      end
   end
end

return apprc


GetNextMirror:

parse arg lURL

pURL = pos(hobbesURL, lURL)
if pURL > 1 then do
   mURL = left(lURL, pURL - 1) || hobbesURL2 || substr(lURL, pURL + length(hobbesURL))
   return mURL
end
else if pURL = 1 then do
   mURL = hobbesURL2 || substr(lURL, pURL + length(hobbesURL))
   return mURL
end

pURL = pos(hobbesURL2, lURL)
if pURL > 1 then do
   mURL = left(lURL, pURL - 1) || hobbesURL3 || substr(lURL, pURL + length(hobbesURL2))
   return mURL
end
else if pURL = 1 then do
   mURL = hobbesURL3 || substr(lURL, pURL + length(hobbesURL2))
   return mURL
end

return ''


DeleteDownloadedApp:

call Directory setupDir
address cmd '@deltree /y' setupDir || '\' || appname '> nul'
call Directory originalDir

if rc <> 0 then do
   return 1
end

return 0

/* check if a newer version of Suntan Special is available */
CheckSuntan:

editionFile = SuntanDir || '\edition.dat'
currentDateTime = CdiDate(editionFile)
if currentDateTime = -1 then do
   call LogCdi "CheckSuntan: missing" editionFile
   return
end

sunApp = 'suntan'
cf = getcf(sunApp)
if cf = '' then return

availableDateTime = GetProductDate(cf)
call LogCdi "Suntan Special current:" currentDateTime "available:" availableDateTime
if availableDateTime = -1 then return

editionLine = linein(editionFile)
call stream editionFile, 'c', 'close'
parse var editionLine editionName ', version' sunVersion

if availableDateTime > currentDateTime then do
   call LogCdi "An update to Suntan Special is available."
   ApplicationStatus = 'Update Available'
   cr = d2c(13)
   message = "A newer version of Suntan Special is available." || cr || cr || cr ||,
             "Currently Installed: version" sunVersion "(" || currentDateTime || ")" || cr ||,
             "Available: (" || availableDateTime || ")" || cr || cr ||,
             "Install" sunApp "to upgrade to the newer version of Suntan Special."
   call CDMessage message
   call CDMessageWait
   call LogCdi '!apped P' || sunApp '(' || ApplicationStatus || ')'
end
else do
   call LogCdi "Suntan Special is up to date."
end

return

/* find the details for an application */
FindDetails:

parse arg myCf
if cf = '' then do
   msg = "No app could be found."
   call LogCdi '!apped D' || msg
   return msg
end

proc.0 = 0
productName = ''
echo = 0
breakLine = 0
descriptionFound = 0

productName = getCdiVar('productName' myCf)
productURL = getCdiVar('productURL' myCf)
extraZip = getCdiVar('extraZip' myCf)
appsDir = cdglobal('appsDir')
installDir = getCdiVar('installDir' myCf)
productDate = GetProductDate(myCf)
productCategory = getCdiVar('productCategory' myCf)

rc = stream(myCf)
if rc <> 'UNKNOWN' then do
   call LogCdi "FindDetails(" || myCf || ") error" rc
   return ''
end

do while lines(myCf)
   rexxStmt = linein(myCf)
   pd = pos('Description', rexxStmt)
   pc = pos('Copyright', rexxStmt)
   pp = pos('Procedure', rexxStmt)

   if pc > 0 then do
      echo = 0
   end

   if pp > 0 & echo = 1 then do
      breakLine = 1
      call addLine strip(substr(rexxStmt, pos('*', rexxStmt) + 1))
   end

   else if echo = 1 then do
      if strip(rexxStmt) = '*' then do
          call addLine ' '
      end
      else do
         call addLine strip(substr(rexxStmt, pos('*', rexxStmt) + 1))
      end
   end

   if pd > 0 then do
      if descriptionFound = 0 then do
         echo = 1
         descriptionFound = 1
         call addLine ' '
      end
   end
end

call stream myCf, 'c', 'close'

/* assemble message */
msg = "Product Name:" productName
if Applicationstatus <> '' then do
   msg = msg || cr || "Status:" Applicationstatus
end

if translate(right(myCf, 7)) = 'CDI.CMD' then do
   appSource = left(myCf, length(myCf) - 8)
end
else do
   appSource = 'web'
end
msg = msg || cr || "Source:" appSource

if productDate <> '' then do
   msg = msg || cr || "Date available:" prettyDate(productDate)
end

iTime = scanDB(appname)
if iTime <> '' then do
   msg = msg || cr || "Date installed:" prettyDate(iTime)
end

if productCategory <> '' then do
   msg = msg || cr || "Product Category:" productCategory
end

if installDir <> '' then do
   msg = msg || cr || "Installation Directory:" installDir
end

if productURL <> '' then do
   msg = msg || cr || "Web site:" productURL
end

if extraZip <> '' then do
   msg = msg || cr || "Additional files required:" extraZip
end

do i=1 to proc.0
   if strip(proc.i) = '' then do
      msg = msg || cr || cr
   end
   else do
      msg = msg || strip(proc.i) || ' '
   end
end

return msg


/* getcf(appname)
 *
 */
getcf:

parse arg appName

localcf = ''
if cdglobal('showLocalApps') = 1 then do
   localpath = GetApplication(appName)
   if localpath <> '' then do
      localcf = stream(localpath || '\cdi.cmd', 'c', 'query exists')
   end
end

webcf = ''
if cdglobal('showWebApps') = 1 then do
   updateDir = cdglobal('suntanDir') || '\update'   
   webcf = stream(updateDir || '\' || appName, 'c', 'query exists')
end

if localcf = '' then do
   if webcf = '' then do
      return ''
   end
   else do
      cf = webcf
   end
end
else do
   if webcf = '' then do
      cf = localcf
   end
   else do
      call SysFileTree localcf, 'localdate.', 'FT'
      do i=1 to localdate.0
         parse var localdate.i time size flags localname
         parse var time t1 '/' t2 '/' t3 '/' t4 '/' t5
         localtime = strip(t1 || t2 || t3 || t4 || t5)
      end
      call SysFileTree webcf, 'webdate.', 'FT'
      do i=1 to webdate.0
         parse var webdate.i time size flags webname
         parse var time t1 '/' t2 '/' t3 '/' t4 '/' t5
         webtime = strip(t1 || t2 || t3 || t4 || t5)
      end
      /* Debug call to show web time and local time in log
      call LogCdi "*** getcf(" || appName || ")" localcf localtime webcf webtime */
      /* add 100 to account for daylight saving time */
      if localtime + 100 >= webtime then do
         cf = localcf
      end
      else do
         cf = webcf
      end
   end
end
return cf

/* GetApplication(appname)
 * returns full path to appname
 * or empty string if appname is not found on sourcepath
 */
GetApplication:
parse arg command
sourcePath = cdglobal('sourceDir')

/* parse source path into source directories */
j = 0
do while sourcePath <> ''
   parse var sourcePath sourceDir ';' sourcePath
   if sourceDir \= '' then do
      j = j + 1
      Dir.j = sourceDir
   end
end
Dir.0 = j

do j = 1 to Dir.0
   testApp = Dir.j || '\' || command
   if stream(testApp || '\cdi.cmd', 'c', 'query exists') <> '' then do
      return testApp 
   end
end

/* appname was not found; return empty string */
call Directory originalDir
return ""


/* add one line to the proc array */
addLine:

parse arg detail

x = proc.0
x = x + 1
proc.x = detail
proc.0 = x

return



/* GetProductDate requires CdiDate */

GetProductDate:
/*
 * argument: file to scan for productDate
 */
parse arg cf
productDate = getCdiVar('productDate' cf)
if productDate <> '' then return productDate
call LogCdi "missing productDate in" cf

do while lines(cf)
   rexxStmt = linein(cf)
   pp = pos('productDate', rexxStmt)
   ep = pos('=', rexxStmt)
   if pp > 0 & pp < ep then do
      interpret rexxStmt
      call stream cf, 'c', 'close'
      return productDate
   end
end

call stream cf, 'c', 'close'

return CdiDate(cf)

CdiDate:

parse arg cdiName

if RxFuncQuery('SysGetFileDateTime') = 0 then do
   return SysGetFileDateTime(cdiName)
end

/* Fallback if SysGetFileDateTime is not found */
call SysFileTree cdiName, 'cdidat.'
if cdidat.0 <> 1 then do
   call LogCdi "Error: CdiDate found" cdidat.0 "files named" cdiName
   return ''
end
mm = left(cdidat.1, 2)
dd = substr(cdidat.1, 4, 2)
yy = substr(cdidat.1, 7, 2)
hh = substr(cdidat.1, 11, 2)
mn = substr(cdidat.1, 14, 2)
if yy < 80 then yy = '20' || yy
else yy = '19' || yy
if left(mm, 1) = ' ' then mm = '0' || right(mm, 1)
if left(hh, 1) = ' ' then hh = '0' || right(hh, 1)
if substr(cdidat.1, 16, 1) = 'p' then hh = hh + 12

return yy || '-' || mm || '-' || dd hh || ':' || mn || ':00'

/* load the date installed for one application */
scanDB:

parse arg scanapp
scanapp = translate(scanapp)

appTime = ''
dbfile = suntanDir || '\appdb.txt'
do while lines(dbfile) > 0
   dbline = linein(dbfile)
   parse var dbline installDate ',' appDir ',' appArg ',' retcode ',' installTime
   if (lastpos('\', appDir) > 0) & (retcode = 0) then do
      scanName = translate(strip(substr(appDir, lastpos('\', appDir) + 1)))
      if scanapp = scanName then do
         installTime = strip(installTime)
         appY   = substr(installTime, 3, 2)  /* year */
         appMon = substr(installTime, 6, 2)  /* month */
         appD   = substr(installTime, 9, 2)  /* day */
         appH   = substr(installTime, 12, 2) /* hour */
         appMin = substr(installTime, 15, 2) /* minute */
         if datatype(appY)   <> 'NUM' then return ''
         if datatype(appMon) <> 'NUM' then return ''
         if datatype(appD)   <> 'NUM' then return ''
         if datatype(appH)   <> 'NUM' then return ''
         if datatype(appMin) <> 'NUM' then return ''
         appTime = appY || appMon || appD || appH || appMin
      end
   end
end
call stream dbfile, 'c', 'close'

return appTime

/* put up a dialog to offer the installation of one application
 * returns OK or Cancel.
 */
makeInstallPage:

app = arg(1)
parm = arg(2)
cf = arg(3)

if app = '' then return 'Cancel'

if parm = '' then do
   appparm = app
end
else do
   appparm = app || "(" || parm || ")"
end

message = "Suntan Special will install" appparm || "." || cr || cr || FindDetails(cf)
response = MessageDialog('SingleStep', 5, message)

return response

CheckContext:
contextFile = suntanDir || '\context.dat'
contextBackup = suntanDir || '\context.bak'
if stream(contextFile, 'c', 'query size') = 0 then do
   call LogCdi "Removing zero length context file"
   call SysFileDelete contextFile
   address cmd '@copy' contextBackup contextFile '> nul'
   if rc <> 0 then do
      call LogCdi "Error" rc "restoring backup context file."
   end
end
return

/* Application settings editor 
ApplicationSettingsEd:

pipeName = '\pipe\suntan'
dialogName = 'Settings'
response = ''
suntanDir = strip(SysIni('User', 'Suntan Special', 'Settings'), 't', d2c(0))
if suntanDir = 'ERROR:' then suntanDir = '.'
cr = d2c(13)
instructions = "Settings Editor" || cr || cr ||,
               "Single click each line to get a description of the item",
               "on that line. Double click or press enter to select",
               "the setting for editing."

x = dialogName || d2c(10) ||,
    instructions || d2c(10) ||,
    "SetEdCB.cmd"  || d2c(10) ||,
    "SetEdCB.cmd" || d2c(10) ||,
    "~Edit"

if arg(1) = "Wait" then do
   x = x || d2c(10) || "Wait"
end
else do
   x = x || d2c(10) || "Continue"
end

x = x || d2c(10) || 'appsDir'
x = x || d2c(10) || 'setupDir'
x = x || d2c(10) || 'sourceDir'
x = x || d2c(10) || 'AutoCheckUpdates'
x = x || d2c(10) || 'saveWebApps'
x = x || d2c(10) || 'showNotInstalled'
x = x || d2c(10) || 'showUpdateAvailable'
x = x || d2c(10) || 'showUptoDate'
x = x || d2c(10) || 'showWebApps'
x = x || d2c(10) || 'showLocalApps'
 
call LogCdi "!listpanel" x

if arg(1) = 'Wait' then do
   qname = RxQueue('Create', 'SetEd')
   if qname <> 'SETED' then do
      say 'RxQueue(Create, SetEd) returned' qname
      call RxQueue 'Delete', qname
   end
   oldq = RxQueue('Set', 'SetEd')
   SetEdEnd = LineIn('QUEUE:')
   call RxQueue 'Set', oldq
   call RxQueue 'Delete', 'SetEd'
end

return
*/

 /* ------------------------------------------------------------------ */
 /* function: bubble sort routine from REXX tips and tricks            */
 /*                                                                    */
 /* call:     BubbleSort                                               */
 /*                                                                    */
 /* returns:  nothing                                                  */
 /*                                                                    */
 /* notes:    You must save the elements to sort in the stem "AppList."*/
 /*           stem.0 must contain the number of elements in the stem.  */
 /*                                                                    */
 /*                                                                    */
 BubbleSort: PROCEDURE expose AppList.
 
   do i = AppList.0 to 1 by -1 until flip_flop = 1
     flip_flop = 1
     do j = 2 to i
       m = j - 1
       mName = translate(filespec('name', AppList.m))
       jName = translate(filespec('name', AppList.j))
       if mName >> jName then do
         /* say "Swapping" mName "and" jName */
         xchg = AppList.m
         AppList.m = AppList.j
         AppList.j = xchg
         flip_flop = 0
       end /* if AppList.m ... */
     end /* do j = 2 ... */
   end /* do i = AppList.0 ... */
 return

/* convert timestamp into readable date and time
 * datetime = prettyDate(timestamp)
 */
prettyDate:

parse arg uglyTime
if pos('-', uglyTime) > 0 then do
   prettyMonth = substr(uglyTime, 6, 2) + 0
   
   return substr(uglyTime, 9, 2) month.prettyMonth substr(uglyTime, 3, 2) substr(uglyTime, 12, 5)
end

prettyMonth = substr(uglyTime, 3, 2) + 0

return substr(uglyTime, 5, 2) month.prettyMonth left(uglyTime, 2) substr(uglyTime, 7, 2) || ':' || right(uglyTime, 2)


/* get Cdi Variable */
getCdiVar:

parse arg myVar myCdiFile

/* for debugging
call LogCdi "DEBUG: getCdiVar(" || myVar || "," myCdiFile || ")" */

if myVar = '' then do
   call LogCdi "getCdiVar called with no myVar argument"
   return ''
end

if myCdiFile = '' then do
   call LogCdi "getCdiVar called with no myCdiFile argument"
   return ''
end

interpret myVar "=''"

call SysFileSearch myVar, myCdiFile, 'file.'
do cdindex=1 to file.0
   pp = pos(myVar, file.cdindex)
   ep = pos('=', file.cdindex)
   if pp > 0 & pp < ep then do
      if right(file.cdindex, 1) = ',' then do
         return ','
      end
      else do
         interpret file.cdindex
         return value(myVar)
      end
   end
end

return value(myVar)


/* tcpip32()
   returns 1 if 32-bit TCP/IP is installed
   otherwise returns 0 */
tcpip32:

tcpdll = FindBootDrive() || ':\mptn\dll\tcpip32.dll'
if stream(tcpdll, 'c', 'query exists') <> "" then return 1

return 0


/* FTPremoteFile(url, userid, password, localFile) */
FTPremoteFile:

url = arg(1)
userid = arg(2)
password = arg(3)
filespec = arg(4)

if pos('ftp://', url) <> 1 then do
   call LogCdi "ApplicationEd FTPremoteFile url must begin with ftp://"
   return 1
end
parse var url 'ftp://' host '/' localspec
filespec = substr(url, lastpos('/', url) + 1)
if lastpos('/', localspec) > 0 then do
   localdir = left(localspec, lastpos('/', localspec) - 1)
end
else do
   localdir = ''
end

if debug <> 0 then do
   say "host =" host"!"
   say "userid =" userid"!"
   say "password =" password"!"
   say "localdir =" localdir"!"
   say "filespec =" filespec"!"
end

if debug <> 0 then do
   say 'contacting host' host 'to retrieve file' filespec
end

/* load the ftp library */
rc = RxFuncAdd("FtpLoadFuncs","rxFtp","FtpLoadFuncs")
if rc = -1 then do
   call LogCdi "RxFuncAdd failed"
   return 1
end

rc = FtpLoadFuncs()
if rc = -1 then do
   call LogCdi "FtpLoadFuncs failed due to" ftperrno 
   return 1
end

/* open the host */
rc = FtpSetUser(host, userid, password)
if rc = 0 then do
   call LogCdi "FtpSetUser failed"
   return 1
end

/* print operating system */
if debug <> 0 then do
   rc = FtpSys(opSys)
   call LogCdi "host" host "is running" opSys
end

/* set the directory */
rc = FtpChDir(localDir)
say "FtpChDir returned" rc
if rc = 0 then do
   call LogCdi "FtpChDir failed"
   return 1
end

/* get file */
rc = FtpGet(filespec, filespec, "Binary")
if rc = -1 then do
   call LogCdi "FtpGet failed due to" ftperrno 
   return 1
end

/* logoff */
rc = FtpLogoff()
if debug <> 0 then do
   say "FtpLogoff returned" rc
end

rc = FtpDropFuncs()
if rc = -1 then call LogCdi "FtpDropFuncs failed due to" ftperrno

return 0

/* error handler */
syntax:
error:
call LogCdi "Condition" Condition('c') "was raised"
call LogCdi "ApplicationEd.cmd Error" rc "on line" sigl
call LogCdi ErrorText(rc)
if ErrorText(rc) = "File Table full" then do
   call LogCdi "Suntan Special cannot continue and will quit."
   call LogCdi "!quit"
   exit 1
end
return rc
