#!/bin/sh  
# \
exec oagwish "$0" "$@"

#
# $Log: not supported by cvs2svn $
# Revision 1.33  2003/07/18 17:42:21  borland
# Added Linux system.
#
# Revision 1.32  2003/01/23 19:47:07  soliday
# The accelerator subnet changed to accel.ntw0rk
#
# Revision 1.31  1998/06/30 20:19:00  borland
# Now intercepts error that occured when something had a patch but no
# generationed versions (or none where expected).   Doesn't bomb anymore,
# but I don't know what will happen if one tries to do an install of this
# odd balls.
#
# Revision 1.30  1996/11/12 20:26:25  saunders
# Missing argument to clearPatch call added.
#
# Revision 1.29  1996/11/08 23:45:02  saunders
# Patch handling added.
#
# Revision 1.28  1996/11/07 17:03:43  saunders
# User name added to log message.
#
# Revision 1.27  1996/10/28 19:08:23  saunders
# Default filter of apps only (no libs).
#
# Revision 1.26  1996/10/28 19:05:35  saunders
# More bug fixes.
#
# Revision 1.25  1996/10/28 17:44:51  saunders
# Bug fix.
#
# Revision 1.24  1996/10/28 16:03:41  saunders
# All applications in a single CVS module may now be updated to the
# latest version with one button press.
#
# Revision 1.23  1996/10/25 20:02:29  saunders
# Added filter which limits app listbox to stuff from selected CVS
# module.
#
# Revision 1.22  1996/10/25 18:47:27  saunders
# Now displays all libraries as well as applications.
# Created new menu entry under Options which brings up dialog box
# of application filters. Can filter out libs or apps if desired.
# GUI now appears before app directories are scanned.
#
# Revision 1.21  1996/10/24 20:23:48  saunders
# Fixed bug whereby change in official app version was not recorded
# "graphically".
#
# Revision 1.20  1996/08/12 14:45:42  saunders
# Application version list now properly sorted.
#
# Revision 1.19  1996/08/08 21:59:22  saunders
# Added Version delete dialog.
#
# Revision 1.18  1996/06/25 15:40:37  saunders
# Changed log message so it now includes applicationArch tag.
#
# Revision 1.17  1996/06/25 15:24:44  saunders
# Added "Rescan" button and Options menu item to disable warning dialogs.
#
# Revision 1.16  1996/06/13 19:28:37  saunders
# Yet another bug fix.
#
# Revision 1.15  1996/06/13 19:16:30  saunders
# Small bug fix.
#
# Revision 1.14  1996/06/13 18:51:08  saunders
# Logging enabled only on helios subnet.
#
# Revision 1.13  1996/06/13 18:43:48  saunders
# Added "New Version Only" application list filter.
#
# Revision 1.12  1996/06/10 19:11:20  saunders
# Changed logMessage cmds to use default logDaemon.
#
# Revision 1.11  1996/06/10 17:57:49  saunders
# Fixed bug. Unitialized var versionNotesList.
#
# Revision 1.10  1996/06/04 19:44:01  saunders
# Small bug fix. Uninitialized var.
#
# Revision 1.9  1996/06/04 19:25:20  saunders
# Added dialog for viewing and deleting private test versions.
#
# Revision 1.8  1996/05/16 15:57:12  saunders
# Accidentally left debug directories enabled.
#
# Revision 1.7  1996/05/16 15:46:29  saunders
# Close to completing beta version.
#
# Revision 1.6  1996/05/15 21:10:53  saunders
# Added context help.
#
# Revision 1.5  1996/05/15 20:50:25  saunders
# Official link can now be changed.
#
# Revision 1.4  1996/05/15 15:36:22  saunders
# Notes files now displayed.
#
# Revision 1.3  1996/05/14 18:12:06  saunders
# Now shows app versions and their state.
#
# Revision 1.2  1996/05/14 01:14:20  saunders
# Intermediate stage.
#
# Revision 1.1  1996/05/10 21:47:24  saunders
# Initial Commit.
#
#

set auto_path [linsert $auto_path 0  /usr/local/oag/apps/lib/$env(HOST_ARCH)]
set auto_path [linsert $auto_path 0 /usr/local/oag/lib_patch/$env(HOST_ARCH)]

APSDebugPath

set CVSRevisionAuthor "\$Revision: 1.34 $ \$Author: soliday $"

set OAGDir /usr/local/oag/apps
set EPICSDir /usr/local/epics/extensions

set OAGbinDir $OAGDir/bin
set OAGlibDir $OAGDir/lib
set OAGdocDir $OAGDir/notes
set OAGsrcDir $OAGDir/src
set EPICSbinDir $EPICSDir/bin
set EPICSlibDir $EPICSDir/lib
set EPICSdocDir $EPICSDir/notes
set EPICSsrcDir $EPICSDir/src

set binDirList [list $EPICSbinDir $OAGbinDir]
set libDirList [list $EPICSlibDir $OAGlibDir]

set libPatchDir /usr/local/oag/lib_patch
set binPatchDir /usr/local/oag/bin_patch
set patchDirList [list $binPatchDir $libPatchDir]

set logDir /home/helios/oagData/logDaemonData
set sourceId appVersionAudit
set serviceId oag
set logFile $sourceId.log
set userEmail "$env(USER)@aps.anl.gov"
set userGroups [split [exec groups]]
set userName [exec whoami]
set userBin $env(HOME)/bin/$env(HOST_ARCH)
set arch $env(HOST_ARCH)
set appList {}
set filteredAppList {}
set appFilter1 "All apps"
set appFilter2 "apps"

set moduleSelection "ALL"
set appSelection ""
set versionSelection ""
set warningDialogs 1

#
# Procedure Definitions
#

#
# Filter out appList according to selected filters
#
proc filterApps {appList} {
    global appFilter1 appFilter2 moduleSelection
    global appVersions appLink binAppList libAppList
    global EPICSsrcDir OAGsrcDir

    # Apply filter2: all apps and libs, libs only, or apps only
    set filter2List {}
    switch $appFilter2 {
	all {
	    set filter2List $appList
	}
	libs {
	    foreach app $appList {
		set ix [lsearch -exact $libAppList $app]
		if {$ix != -1} {
		    lappend filter2List $app
		}
	    }
	}
	apps {
	    foreach app $appList {
		set ix [lsearch -exact $binAppList $app]
		if {$ix != -1} {
		    lappend filter2List $app
		}
	    }
	}
	default {
	    puts stderr "applicationBrowse: unknown appFilter2"
	}
    }

    # Apply filter3: apps/libs only from selected CVS module
    set filter3List {}
    if {![string compare $moduleSelection ALL]} {
	set filter3List $filter2List
    } else {
	set moduleAppList {}
	if ![catch {exec find $EPICSsrcDir/$moduleSelection -type f} res] {
	    set res [split $res \n]
	    foreach file $res {
		lappend moduleAppList [file tail $file]
	    }
	}
	if ![catch {exec find $OAGsrcDir/$moduleSelection -type f} res] {
	    set res [split $res \n]
	    foreach file $res {
		lappend moduleAppList [file tail $file]
	    }
	}
	foreach app $filter2List {
	    set ix [lsearch -exact $moduleAppList $app]
	    if {$ix != -1} {
		lappend filter3List $app
	    }
	}

    }

    # Apply filter1: all apps or ones with new version only
    set filter1List {}
    if {![string compare "Apps with new version only" $appFilter1]} {
	foreach app $filter3List {
	    if [info exists appVersions($app)] {
		set ix [lsearch -exact $appVersions($app) $appLink($app)]
		if {$ix == -1} {
		    set ix [lsearch $appVersions($app) *.patch]
		    if {$ix == -1} {
			continue
		    }
		} 
		incr ix
		set nextVer [lindex $appVersions($app) $ix]
		while {[string length $nextVer]} {
		    if {![string match *.patch* $nextVer]} {
			lappend filter1List $app
			break
		    }
		    incr ix
		    set nextVer [lindex $appVersions($app) $ix]
		}
	    }
	}
    } else {
	set filter1List $filter3List
    }

    return $filter1List
}

#
# Fill application listbox with current appList
#
proc refreshAppList {{newArchitecture 0}} {
    global appWidget versionWidget notesWidget status appList
    global filteredAppList

    set status "Please wait, loading application list..."
    $versionWidget delete 1.0 end
    $notesWidget delete 1.0 end
    update idletasks

    $appWidget delete 0 end

    if {$newArchitecture} {
	setAppList
	setAppStatus
    }

    set filteredAppList [filterApps $appList]

    if {$newArchitecture} {
	# Set listbox width so that longest item fits
	set minWidth 20
	foreach item $filteredAppList {
	    set itemWidth [string length $item]
	    if {$itemWidth > $minWidth} {
		set minWidth $itemWidth
	    }
	}
	$appWidget configure -width $minWidth
    }

    eval {$appWidget insert end} $filteredAppList
    set status "done."
}

#
# Process click on application
#
proc appSelection {item doubleClick} {
    global appVersions appNotes versionWidget notesWidget status
    global appVersionStatus appSelection officialFirst officialLast

    if {$doubleClick == 0} {
	set appSelection $item
	$versionWidget delete 1.0 end
	$notesWidget delete 1.0 end
	if [info exists appVersions($item)] {
	    foreach version $appVersions($item) {
		switch $appVersionStatus($version) {
		    official {
			# Save location of official version
			set officialFirst [$versionWidget index \
					     "insert"]
			$versionWidget insert end $version\n green
			set officialLast [$versionWidget index \
					    "insert"]
		    }
		    normal {
			$versionWidget insert end $version\n normal
		    }
		    patch {
			$versionWidget insert end $version\n yellow
		    }
		}
	    }
	    set status "Select a version."
	} else {
	    set status "No versions found for $item"
	}
    }
}

#
# Process click on version
#
proc versionSelection {versionWidgetIndex {showNotes 1}} {
    global appDir status notesWidget versionWidget versionSelection
    global versionSelectionFirst versionSelectionLast

    # Get version name from versionWidgetIndex
    set first [$versionWidget index "$versionWidgetIndex linestart"]
    set last [$versionWidget index "$versionWidgetIndex lineend + 1 chars"]
    set versionSelection \
      [$versionWidget get $first "$versionWidgetIndex lineend"]
    if {![string compare $versionSelection ""]} {
	return
    }

    set binDir $appDir($versionSelection)
    set notesDir "$binDir/../../notes"

    # Update graphics to reflect selected version
    $versionWidget tag remove selected 0.0 $first
    $versionWidget tag add selected $first $last
    $versionWidget tag remove selected $last end
    set versionSelectionFirst $first
    set versionSelectionLast $last

    # If a patch is selected, only show patch date
    if {[string match *.patch* $versionSelection] && $showNotes} {
	# Clear contents of notesWidget
	$notesWidget delete 1.0 end
	
	set prod ""
	regexp {(.+).patch} $versionSelection match prod
	# First put date of selected version into notesWidget
	set lsCmd "exec ls -l $binDir/$prod"
	if {![catch $lsCmd lsOut]} {
	    set outList [split $lsOut " "]
	    set outLen [llength $lsOut]
	    set month [lindex $lsOut [expr $outLen - 4]]
	    set day [lindex $lsOut [expr $outLen - 3]]
	    set time [lindex $lsOut [expr $outLen - 2]]
	    $notesWidget insert end "Version Date: $month $day $time\n\n"
	}

    } elseif {$showNotes} {
	# Construct name of notesList file from version selection
	regexp {(.+)(.[0-9][0-9][0-9][0-9][0-9]$)} $versionSelection match prod ver
	set notesIndex $binDir/${prod}_notesList${ver}
	
	# Clear contents of notesWidget
	$notesWidget delete 1.0 end
	
	# First put date of selected version into notesWidget
	set lsCmd "exec ls -l $binDir/$versionSelection"
	if {![catch $lsCmd lsOut]} {
	    set outList [split $lsOut " "]
	    set outLen [llength $lsOut]
	    set month [lindex $lsOut [expr $outLen - 4]]
	    set day [lindex $lsOut [expr $outLen - 3]]
	    set time [lindex $lsOut [expr $outLen - 2]]
	    $notesWidget insert end "Version Date: $month $day $time\n\n"
	}
	
	# Read contents of notesList file to get list of notes files
	#   associated with this version.
	set versionNotesList {}
	if [catch "open $notesIndex r" fileId] {
	    set status "No release notes for $versionSelection"
	    return
	} else {
	    while {[gets $fileId line] >= 0} {
		lappend versionNotesList $notesDir/$line
	    }
	    close $fileId
	}
	
	# Put contents of all notes files into Release Notes window
	foreach notesFile $versionNotesList {
	    if [catch "open $notesFile r" fileId] {
		set status "$fileId"
		continue
	    } else {
		while {[gets $fileId line] >= 0} {
		    $notesWidget insert end $line\n
		}
		close $fileId
	    }
	}
	$notesWidget see end
    }
}

#
# Will attempt to create a symbolic link between selected
#  app and selected version in official directory.
#
proc makeVersionOfficial {{globalUpdate 0}} {
    global appSelection versionSelection appDir status appVersions
    global officialFirst officialLast sourceId serviceId versionWidget
    global versionSelectionFirst versionSelectionLast appLink apsNetworkDomain
    global warningDialogs arch appVersionStatus apsScriptUser

    if {![string compare $appSelection ""]} {
	bell
	set status "No application is selected"
	return
    }
    if {![info exists appVersions($appSelection)]} {
	bell
	set status "No versions for this application"
	return
    }
    if {![string compare $versionSelection ""]} {
	bell
	set status "No version is selected"
	return
    }

    # Verify that selected version is dated after any patch. Ie. don't
    #  allow someone to make an older version official if a patch exists.
    set patch 0
    set afterPatch 0
    foreach version $appVersions($appSelection) {
	if {[string match *.patch* $version]} {
	    set patch 1
	} elseif {$patch && ![string compare $version $versionSelection]} {
	    set afterPatch 1
	}
    }
    # Return if versionSelection doesn't have a later date than any patch
    if {$patch && !$afterPatch} {
	if {[string match *.patch $versionSelection]} {
	    set status "$versionSelection is already active"
	} else {
	    set status "$versionSelection is older than the patch"
	}
	return
    }

    # Save old link so we can log it
    set dir $appDir($versionSelection)
    if {[catch "file readlink $dir/$appSelection" res]} {
	set oldLink "none"
	set status "Warning: selection is not a symlink...proceeding anyway"
    } else {
	set oldLink [file tail $res]
	# Return if current official version is same as selection
	if {![string compare $oldLink $versionSelection]} {
	    if {$patch} {
		clearPatch $globalUpdate
	    }
	    set status "$versionSelection already official"
	    return
	}
    }

    # Make sure user knows implications of doing this
    if {$warningDialogs} {
	APSInfoWindow .areyousure -name "Are You Sure?" -infoMessage "You are about to change the official version of\n $appSelection\nfrom: $oldLink\nto:   $versionSelection\nThis will change it for everyone on site. Currently executing instances will continue, but all new invocations will be the new version. Are you sure...?"
	grab .areyousure
	global cancelVersionChange
	set cancelVersionChange 0
	APSDialogBoxAddButton .cancel -parent .areyousure -text \
	  "Cancel Version Change" -command {
	      set cancelVersionChange 1
	      grab release .areyousure
	      destroy .areyousure
	  }
	tkwait window .areyousure
	if {$cancelVersionChange} {
	    return
	}
    }

    # Remove existing link and create new one
    set rmCmd "exec rm -f $dir/$appSelection"
    set lnCmd "exec ln -s $dir/$versionSelection $dir/$appSelection"
    if [file exists $dir/$appSelection] {
	catch $rmCmd
	if [file exists $dir/$appSelection] {
	    bell
	    set status "Unable to remove old link: $dir/$appSelection"
	    return
	}
    }
    if [catch $lnCmd res] {
	bell
	set status "Unable to create link: $res"
	return
    }

    # Remove any patch
    if {$patch} {
	clearPatch $globalUpdate
    }
    
    if {[catch "file readlink $dir/$appSelection" res]} {
	set appLink($appSelection) "none"
	set status "Unable to get new link info: $res"
    } else {
	set appLink($appSelection) [file tail $res]
    }

    # Adjust the versionWidget to show new link
    if {[info exists officialFirst] && [info exists officialLast]} {
	$versionWidget tag remove green $officialFirst $officialLast
    }
    $versionWidget tag add green $versionSelectionFirst $versionSelectionLast
    set officialFirst $versionSelectionFirst
    set officialLast $versionSelectionLast
    set appVersionStatus($oldLink) normal
    set appVersionStatus($versionSelection) official

    update idletasks

    # Log activity
    if {(![string compare $apsNetworkDomain "accel.ntw0rk"]) || (![string compare $apsNetworkDomain "aps4.anl.gov"])} {
	set logCmd "logMessage -sourceId=$sourceId -tag=applicationName $appSelection -tag=user $apsScriptUser -tag=applicationArch $arch -tag=applicationState versionChange -tag=previousVersion $oldLink -tag=newVersion $appLink($appSelection)"
	if [catch "exec $logCmd" res] {
	    bell
	    set status "Unable to log new version for $appSelection: $res"
	    return
	} else {
	    set status "New version of $appSelection logged."
	}
    }

}

proc clearPatch {globalUpdate} {
    global appSelection appDir versionWidget appVersionStatus appVersions
    global officialFirst officialLast versionSelectionFirst
    global versionSelectionLast status

    # Remove any patch
    set patchVersion ${appSelection}.patch
    set patchDir $appDir($patchVersion)
    set rmCmd "exec rm -f $patchDir/$appSelection"
    catch $rmCmd
    if [file exists $patchDir/$appSelection] {
	bell
	set status "Unable to remove patch: $patchDir/$appSelection"
    }
    set ix [$versionWidget search -exact $patchVersion 1.0]
    $versionWidget delete "$ix linestart" "$ix lineend +1 char"
    unset appVersionStatus($patchVersion)
    set appVersions($appSelection) \
      [listDelete $appVersions($appSelection) $patchVersion]
    set versionSelectionFirst \
      [$versionWidget index "$versionSelectionFirst - 1 lines"]
    set versionSelectionLast \
      [$versionWidget index "$versionSelectionLast - 1 lines"]
    
    # If patch is a tcl lib, and we aren't doing a global update,
    #  regenerate the tclIndex.
    if {!$globalUpdate && [string match *.tcl* $appSelection]} {
	set status "Regenerating tclIndex, please wait..."
	update idletasks
	if [catch {auto_mkindex $patchDir *.tcl} res] {
	    bell
	    set status "Unable to regenerate tclIndex: $res"
	}
    }
}

#
# Put symbolic link in $HOME/bin/$HOST_ARCH to selected version.
#
proc makeVersionTest {} {
    global appSelection versionSelection appDir status appVersions
    global userBin warningDialogs

    if {![string compare $appSelection ""]} {
	bell
	set status "No application is selected"
	return
    }
    if {![info exists appVersions($appSelection)]} {
	bell
	set status "No versions for this application"
	return
    }
    if {![string compare $versionSelection ""]} {
	bell
	set status "No version is selected"
	return
    }

    # Make sure user knows implications of doing this
    if {$warningDialogs} {
	APSInfoWindow .areyousure -name "Are You Sure?" -infoMessage "You are about to add a symbolic link to the selected application version in your bin directory. This will override the official version for your account only. You may have to type \"rehash\" in your terminal window to get the private version. To later delete this private version, use the \"View Private Test Versions..\" button. Are you sure...?"
	grab .areyousure
	global cancelVersionChange
	set cancelVersionChange 0
	APSDialogBoxAddButton .cancel -parent .areyousure -text \
	  "Cancel Version Change" -command {
	      set cancelVersionChange 1
	      grab release .areyousure
	      destroy .areyousure
	  }
	tkwait window .areyousure
	if {$cancelVersionChange} {
	    return
	}
    }

    # Check to see if user has a $HOME/bin/$HOST_ARCH directory
    if {![file exists $userBin]} {
	set mkdirCmd "exec mkdir $userBin"
	if [catch $mkdirCmd res] {
	    bell
	    set status "Unable to mkdir $userBin: $res"
	    return
	}
    }

    # Remove any existing link and create new one
    set dir $appDir($versionSelection)
    set rmCmd "exec rm $userBin/$appSelection"
    set lnCmd "exec ln -s $dir/$versionSelection $userBin/$appSelection"
    if [file exists $userBin/$appSelection] {
	if [catch $rmCmd res] {
	    bell
	    set status "Unable to remove previous link: $res"
	    return
	} 
    }
    if [catch $lnCmd res] {
	bell
	set status "Unable to create link: $res"
	return
    }
    set status "Private link to $versionSelection created."
}

#
# Provide dialog showing current collection of private application test
#   versions. Allow selective deletions.
#
proc viewTestVersions {} {
    global userBin

    APSDialogBox .viewTestVersions -name "View Test Versions"
    APSEnableButton .viewTestVersions.buttonRow.ok.button
    APSDialogBoxAddButton .delete -parent .viewTestVersions -text \
      "Delete Selected Test Version" -command \
      { viewTestVersionsDelete .viewTestVersions.userFrame.sl.listbox }
	  

    set dirList [glob -nocomplain $userBin/*]
    set itemList {}
    foreach file $dirList {
	set fname [file tail $file]
	if {[catch "file readlink $file" res]} {
	    set linkname "none"
	} else {
	    set linkname [file tail $res]
	}
	lappend itemList "$fname -> $linkname"
    }

    APSScrolledList .sl -parent .viewTestVersions.userFrame -itemList $itemList
}

proc viewTestVersionsDelete {listWidget} {
    global userBin status

    set index [$listWidget curselection]
    if {![catch {$listWidget get $index} res]} {
	set spaceIndex [string first " " $res]
	set sel [string range $res 0 $spaceIndex]

	# Remove private test link
	set rmCmd "exec rm -f $userBin/$sel"
	if [catch $rmCmd res] {
	    bell
	    set status "Unable to remove test version of $sel: $res"
	    return
	}
	$listWidget delete $index 
	set status "Private link to $sel deleted."
    }
}

#
# Set list of applications for selected architecture. 
#
proc setAppList {} {
    global arch appVersions appNotes appList
    global pathAppList OAGbinDir appDir binDirList libDirList
    global binAppList libAppList patchDirList
    
    set temp {}
    foreach dir $binDirList {
	set temp [concat $temp [glob -nocomplain $dir/$arch/*]]
    }
    set dirList $temp
    set binList $temp
    set temp {}
    foreach dir $libDirList {
	set temp [concat $temp [glob -nocomplain $dir/$arch/*]]
    }
    set dirList [concat $dirList $temp]
    set libList $temp

    foreach file $binList {
	lappend binAppList [file tail $file]
    }
    foreach file $libList {
	lappend libAppList [file tail $file]
    }

    if [info exists appNotes] {unset appNotes}
    if [info exists appVersions] {unset appVersions}
    if [info exists appDir] {unset appDir}

    set appList {}
    set pathAppList {}
    foreach file $dirList {
	set fname [file tail $file]
	set dname [file dirname $file]

	# add file to appropriate list (app, version, or notes)
	if {![regexp {tclIndex|_notesList|.[0-9][0-9][0-9][0-9][0-9]$} $fname]} {
	    lappend pathAppList $file
	    lappend appList $fname
	    set appDir($fname) $dname
	} elseif [regexp {([^_]+)_notesList} $fname match1 prod1] {
	    lappend appNotes($prod1) $match1
	    set appDir($match1) $dname
	} elseif [regexp {(.+).tcl.[0-9][0-9][0-9][0-9][0-9]$} $fname match2 prod2] {
	    lappend appVersions(${prod2}.tcl) $match2
	    set appDir($match2) $dname
	} elseif [regexp {(.+).[0-9][0-9][0-9][0-9][0-9]$} $fname match3 prod3] {
	    lappend appVersions($prod3) $match3
	    set appDir($match3) $dname
	} 
    }
    # Now scan through appVersions to see if there are any apps
    #  which have serial numbered entries, but no symlink pointing
    #  to any of them. Ie. an application which has versions, but
    #  nobody has made any of them the official version.
    foreach index [array names appVersions] {
	if {[lsearch -exact $appList $index] == -1} {
            puts stderr "Warning: $index exists but never made official."
	    lappend appList $index
	    set dir $appDir([lindex $appVersions($index) 0])
	    set appDir($index) $dir
	    lappend pathAppList $dir/$index
	    if {[string match */lib/* $dir]} {
		lappend libAppList $index
	    } else {
		lappend binAppList $index
	    }
	}
    }

    # Sort the application and versions lists
    set appList [lsort $appList]
    foreach index [array names appVersions] {
	set appVersions($index) [lsort $appVersions($index)]
    }

    # For each app, search for a patch. If there is one, add it
    #  to the proper slot in the list of versions for that app.
    foreach app $appList {
	set patchDir ""
	foreach dir $patchDirList {
	    if {[file exists $dir/$arch/$app]} {
		set patchDir $dir/$arch
		break
	    }
	}
	# If a patch exists
	if {[string length $patchDir]} {
	    set patchDate [file mtime $patchDir/$app]
	    if {![info exists appVersions($app)]} {
		set appVersions($app) {}
		set llen 0
	    } else {
		set llen [llength $appVersions($app)]
	    }
	    set patchIndex 0
            set versionFound 0
	    for {set i 0} {$i < $llen} {incr i} {
		set versionPath $appDir($app)/[lindex $appVersions($app) $i]
                if ![file exists $versionPath] {
                    puts stderr "Warning: file not found: $versionPath"
                    continue
                }
                set versionFound 1
		set versionDate [file mtime $versionPath]
		if {$patchDate > $versionDate} {
		    set patchIndex [expr $i + 1]
		}
	    }
            set appVersions($app) \
              [linsert $appVersions($app) $patchIndex ${app}.patch]
            set appDir(${app}.patch) $patchDir
	}
    }
        
}

#
# Take application list, and set various arrays with application state
# information (ie. what version is official, what versions are patches, etc)
#
proc setAppStatus {} {
    global logDir logFile appLink appNotes appVersions appVersionStatus
    global pathAppList
    if [info exists appLink] {unset appLink}
    if [info exists appVersionStatus] {unset appVersionStatus}

    foreach app $pathAppList {
	set fname [file tail $app]
	# If app is a sym link, find it's destination and save it
	if {[catch "file readlink $app" res]} {
	    set appLink($fname) "none"
	} else {
	    set appLink($fname) [file tail $res]
	}
	# for each version, determine what state it is in
	if [info exists appVersions($fname)] {
	    set hasPatch 0
	    foreach version $appVersions($fname) {
		if {[string match *.patch* $version]} {
		    set hasPatch 1
		    break
		}
	    }
	    foreach version $appVersions($fname) {
		if {![string compare $version $appLink($fname)] && !$hasPatch} {
		    set appVersionStatus($version) official
		} elseif {[string match *.patch* $version]} {
		    set appVersionStatus($version) patch
		} else {
		    set appVersionStatus($version) normal
		}
	    }
	}
    }
}

#
# Bring up dialog box which allows deletion of selected application versions
#
proc deleteDialog {} {
    global userName status

    if [string compare $userName "oag"] {
	bell
	set status "Only user oag is allowed to delete unused application versions."
	return
    }
    if {![winfo exists .deleteDialog]} {
	APSDialogBox .deleteDialog -name Delete
	APSEnableButton .deleteDialog.buttonRow.ok.button
	APSButton .delete -parent .deleteDialog.userFrame -highlight 1 -text \
	  "Delete Selected Version" -command deleteVersion -contextHelp \
	  "Permanently deletes the currently selected application version."
    }
}

#
# Bring up dialog box which allows an entire CVS module of apps/libs to
#   be updated to the latest version
#
proc globalVersionUpdateDialog {} {
    global status

    if {![winfo exists .globalUpdateDialog]} {
	APSDialogBox .globalUpdateDialog -name "Global Update"
	APSEnableButton .globalUpdateDialog.buttonRow.ok.button	
	message .globalUpdateDialog.userFrame.msg -text "This dialog will update all applications in a selected CVS module to the latest version. You may only perform a global version update for a set of applications from a single CVS module. Please use the Options/Filter dialog to select a CVS module."
	pack .globalUpdateDialog.userFrame.msg -side top
	APSButton .update -parent .globalUpdateDialog.userFrame -highlight 1 \
	  -text "Update All to Latest Version" -command globalVersionUpdate \
	  -packOption "-side top -padx 1 -pady 1" \
	  -contextHelp "Updates all applications in the selected CVS module to the latest version"
    } else {
	raise .globalUpdateDialog
    }
}

#
# Updates all the applications in the application listbox to the
#  latest version. This only works if listbox is filtered down to
#  one CVS module.
#
proc globalVersionUpdate {} {
    global moduleSelection filteredAppList status appVersions
    global appSelection warningDialogs versionWidget
    global OAGlibDir libPatchDir arch

    if {![string compare $moduleSelection ALL]} {
	APSAlertBox .globalUpdateAlert -errorMessage "You may only perform a global update on applications from a single CVS module."
	return
    }

    set status "Begin Global Update for $moduleSelection"
    set saveWarningFlag $warningDialogs
    set warningDialogs 0
    set rebuildIndex 0

    foreach app $filteredAppList {
	set status "Updating to latest version of $app"
	set appSelection $app
	if {[string match *.tcl* $app]} {
	    set rebuildIndex 1
	}
	if [info exists appVersions($app)] {
	    appSelection $app 0
	    versionSelection [$versionWidget search -exact \
				[lindex $appVersions($app) end] 1.0] 0
	    makeVersionOfficial 1
	} else {
	    bell
	    set status "no versions exist for $app"
	}
    }
    if {$rebuildIndex} {
	set status "Rebuilding tclIndex, please wait..."
	update idletasks
	if [catch {auto_mkindex $OAGlibDir/$arch *.tcl} res] {
	    bell
	    set status "Unable to regenerate tclIndex: $res"
	}
	if [catch {auto_mkindex $libPatchDir/$arch *.tcl} res] {
	    bell
	    set status "Unable to regenerate tclIndex: $res"
	}
    }
    set warningDialogs $saveWarningFlag
    set status "End Global Update for $moduleSelection"
}

#
# Delete the currently selected application version
#
proc deleteVersion {} {
    global appDir versionSelection status appVersionStatus appVersions
    global versionWidget appSelection versionSelectionFirst
    global versionSelectionLast

    if {![string compare $versionSelection ""]} {
	bell
	set status "No version is selected"
	return
    }
    set dir $appDir($versionSelection)
    set ver $dir/$versionSelection

    if {[string compare $appVersionStatus($versionSelection) "normal"]} {
	bell
	set status "You can only delete \"Regular\" versions"
	return
    }
    if [catch {exec rm -f $ver} res] {
	bell 
	set status "Unable to delete $ver: $res"
    } else {
	set status "Deleted $ver"
	unset appVersionStatus($versionSelection)
	set ix [$versionWidget search -exact $versionSelection 1.0]
	$versionWidget delete "$ix linestart" "$ix lineend +1 char"
	set appVersions($appSelection) \
	  [listDelete $appVersions($appSelection) $versionSelection]

	#set cur [$versionWidget index @%x,%y]
	set first [$versionWidget index "$ix linestart"]
	set last [$versionWidget index "$ix lineend + 1 chars"]
	
	$versionWidget tag remove selected 0.0 $first
	$versionWidget tag add selected $first $last
	$versionWidget tag remove selected $last end
	set versionSelection [$versionWidget get $first "$ix lineend"]
	
	set versionSelectionFirst $first
	set versionSelectionLast $last
	if {[string compare $versionSelection ""]} {
	    versionSelection $versionSelection
	}
    }
}

proc listDelete {list value} {
    set ix [lsearch -exact $list $value]
    if {$ix >=0} {
	return [lreplace $list $ix $ix]
    } else {
	return $list
    }
}

#
# Bring up dialog box which allows filtering out items in application
#  list.
#
proc filterDialog {} {
    global status

    if {![winfo exists .filterDialog]} {
	APSDialogBox .filterDialog -name "Application Filters"
	APSEnableButton .filterDialog.buttonRow.ok.button
	.filterDialog.buttonRow.ok.button configure -command {
	    destroy .filterDialog
	    refreshAppList
	}
	APSButton .apply -parent .filterDialog.buttonRow -text \
	  "Apply" -command refreshAppList -contextHelp \
	  "Pass the full application list through selected filters."

	APSFrameGrid .fg -parent .filterDialog.userFrame -xList {l r}
	set leftFrame .filterDialog.userFrame.fg.l
	set rightFrame .filterDialog.userFrame.fg.r

	APSRadioButtonFrame .filter1 -parent $leftFrame \
	  -buttonList {"All apps" "Apps with new version only"} \
	  -valueList {"All apps" "Apps with new version only"} \
	  -variable appFilter1 -label "Version Filter" \
	  -contextHelp "Select \"All apps\" to see all applications, or \"Apps with new version only\" too see applications for which versions more recent than the current official version exist."

	APSRadioButtonFrame .filter2 -parent $leftFrame \
	  -buttonList {"Apps and libraries" "Libraries only" "Apps only"} \
	  -valueList {all libs apps} -variable appFilter2 \
	  -label "App/Lib Filter" -contextHelp "View all applications, libraries only, or applications only."

	APSFrame .filter3 -parent $rightFrame -label "Filter by CVS module" \
	  -contextHelp "Show only applications and/or libraries which are part of the selected CVS module"
	tkwait visibility $rightFrame.filter3
	
	set moduleList [concat {ALL} [getModuleList]]
	APSScrolledList .modules -parent $rightFrame.filter3.frame \
	  -itemList $moduleList -callback moduleSelectionCallback
    } else {
	raise .filterDialog
    }
}

proc moduleSelectionCallback {item doubleClick} {
    global moduleSelection
    if {$doubleClick == 0} {
	set moduleSelection $item
    }
}

#
# Return a list of cvs modules in the EPICS/extensions and OAG repositories
#
proc getModuleList {} {
    global OAGsrcDir EPICSsrcDir

    set dirList [glob -nocomplain $EPICSsrcDir/* ]
    set dirList [concat $dirList [glob -nocomplain $OAGsrcDir/* ]]

    set appList {}
    foreach file $dirList {
	set fname [file tail $file]
	if {[file isdirectory $file] && \
	      [string compare $fname "CVS"]} {
	    lappend appList $fname
	}
    }
    set appList [lsort $appList]
    return $appList
}

#
# Build up Graphical Interface
#

APSApplication . -name applicationBrowse -version $CVSRevisionAuthor \
  -overview "This utility allows you to browse the available list of application versions along with their release notes. You can change a given application's version and make that official (if you have permission). Or, alternately, you can select a version for a private (your account only) test."

APSMenubarAddMenu .options -parent .menu -text Options
.menu.options.menu add check -label "Warning Dialogs" -variable warningDialogs
.menu.options.menu add command -label "Filters..." -command filterDialog

APSMenubarAddMenu .restricted -parent .menu -text Restricted
.menu.restricted.menu add command -label "Global Version Update Dialog..." \
  -command globalVersionUpdateDialog
.menu.restricted.menu add command -label "Delete Dialog..." -command deleteDialog

#
# Scrolled Status
#
set status "Select an application from listbox."
APSScrolledStatus .ss -parent .userFrame -textVariable status -contextHelp \
  "Provides hints and execution status." -withButtons 1
APSRepack .userFrame.ss -fill x

APSFrameGrid .fg0 -parent .userFrame -xList {a}
#
# Build up top frame (below status)
#
APSRadioButtonFrame .arch -parent .userFrame.fg0.a -label "Architecture:" \
  -buttonList {sun4 solaris linux} -valueList {sun4 solaris Linux} -variable arch \
  -commandList {"refreshAppList 1" "refreshAppList 1" "refreshAppList 1"} -orientation horizontal \
  -packOption "-side left" -contextHelp "The list of available applications and versions differ depending on whether you are running SunOS (sun4) or Solaris (solaris). Select which of these architecture's applications you wish to browse here."

APSButton .rescan -parent .userFrame.fg0.a -packOption "-side left" \
  -text "Rescan Apps" -command "refreshAppList 1" -contextHelp "Rescans the application directories and reloads the application list."

APSFrameGrid .fg1 -parent .userFrame -xList {a}
set topGrid .userFrame.fg1.a

APSFrame .appFrame -parent $topGrid -packOption "-side left" \
  -label "Application" -contextHelp "Select the application of interest"

#
# Get application list for current architecture, and set global arrays
#  with status information on versions and notes files. Initialize 
#  scrolled list with application list.
#

APSScrolledList .app -parent $topGrid.appFrame.frame -itemList $appList \
  -callback appSelection -height 17 -contextHelp \
  "Select the application of interest"
set appWidget $topGrid.appFrame.frame.app.listbox

APSFrame .versionFrame -parent $topGrid -packOption "-side left" \
  -label "Versions" -contextHelp "Select a version to view it's release notes. Also, any operations (such as changing the official version) will apply to the currently selected version."

APSScrolledText .version -parent $topGrid.versionFrame.frame \
  -height 20 -width 24 -contextHelp "Select a version to view it's release notes. Also, any operations (such as changing the official version) will apply to the currently selected version."
set versionWidget $topGrid.versionFrame.frame.version.text
$versionWidget configure -cursor left_ptr

APSFrame .notesFrame -parent $topGrid -packOption "-side left" \
  -label "Release Notes" -contextHelp "These are application notes submitted by the programmer who created this application."
APSScrolledText .notes -parent $topGrid.notesFrame.frame -height 20 -width 60 \
  -contextHelp "These are application notes submitted by the programmer who created this application."
set notesWidget $topGrid.notesFrame.frame.notes.text

APSFrameGrid .fg2 -parent .userFrame -xList {left right} -yList {a b c}
set fg2left .userFrame.fg2.left
set fg2right .userFrame.fg2.right

APSButton .makeOfficial -parent $fg2left.a -command makeVersionOfficial \
  -text "Make Version Selection Official..." -contextHelp \
  "This will change the official version of the selected application to the currently selected version. You will be prompted before it occurs."

APSButton .privateTest -parent $fg2left.b -command makeVersionTest \
  -text "Set Up Private Test Version..." -contextHelp \
  "This will set up a symbolic link in your account's bin directory to the currently selected application version. You can then test the application within your user account. To remove this private version or view your current collection of them, use the \"View Your Private Versions...\" dialog box."

APSButton .viewPrivate -parent $fg2left.c -command viewTestVersions \
  -text "View Your Private Versions..." -contextHelp \
  "Brings up a dialog box showing the current list of private application test versions you have. You may delete any of them here."

# Build up color key

set greenMeaning "Current Official Version"
set yellowMeaning "Patch Version Installed"

label $fg2right.a.l \
  -text "Green" -width 10 -highlightthickness 1 \
  -highlightbackground black -bg green
pack $fg2right.a.l -side left
label $fg2right.a.r -text $greenMeaning
pack $fg2right.a.r -side left

label $fg2right.b.l \
  -text "Yellow" -width 10 -highlightthickness 1 \
  -highlightbackground black -bg yellow
pack $fg2right.b.l -side left
label $fg2right.b.r -text $yellowMeaning
pack $fg2right.b.r -side left


# Design color and selection tags for application list widget
proc tagColors {textWidget} {
    $textWidget tag configure selected -relief raised
    $textWidget tag configure green -background green \
      -borderwidth 2 -spacing1 1 -spacing3 1
    $textWidget tag raise green selected
    $textWidget tag configure yellow -background yellow \
      -borderwidth 2 -spacing1 1 -spacing3 1
    $textWidget tag raise yellow selected
    $textWidget tag configure normal -background Gray85 \
      -borderwidth 2 -spacing1 1 -spacing3 1
    $textWidget tag raise normal selected
}

tagColors $versionWidget

# Remove all default Text class bindings for version and notes widgets
bindtags $versionWidget [list $versionWidget . all]
bindtags $notesWidget [list $notesWidget . all]

bind $versionWidget <Button-1> {
    set cur [$versionWidget index @%x,%y]
    versionSelection $cur
}

# Alert user if private test versions are present

set privateTestDirList [glob -nocomplain $userBin/*]
if {[llength $privateTestDirList] != 0} {
    bell
    set status "***************************************************\n Note: I detect one or more private test versions.\n***************************************************"
    bell
}

# Wait for widgets to draw, then fill application listbox.
tkwait visibility $fg2right.b.r
refreshAppList 1

