

# List of procedures:
# APSSCRLogAction : uses logMessage utility to log SCR actions for latter review
# APSSCRSystemChoiceWidget : creates a menu for selecting an SCR system
# APSSCRMakeSystemSubMenu : used by APSSCRSystemChoiceWidget to make submenus
# APSSCRSetSystemList : defines which systems will be used in SCR operations
#   Used to restrict the list of systems.
# APSMakeSCRMatchOptions : returns a list of options for use with sddsprocess to
#   accomplish a specific matching/filtering operation on a snapshot or request file.
# APSCheckSCRSelections : checks for selection of one or more beamline and one or more
#   category items
# APSClearSCRFilterFlags : clears/resets all filter/category/beamline selections.
# APSSCRMakeSnapshotFilename : returns a name to be used for a permanent snapshot file
# APSSaveMachine : makes a permanent or temporary snapshot for a specified machine
# APSSCRCheckSnapshot : Checks a snapshot for bad data, returning the number of bad rows.
#   Puts a line in the logFile with this info, also.
# APSCompareMachine : compares two snapshots, or one snapshot and the present state.
# APSListSCRMatch : creates a listbox for use in selecting snapshots for a specific
#  machine, between specific dates, and matching a specific description pattern.
# APSListSCRMatchSetArray : improved version of APSListSCRMatch that sets values into
#  a user-supplied array.  Presents two lists, one for normal snapshots and one for
#  preferred/reference snapshots.
# APSListSCRMatchSetArrayCallback : callback routine used by APSListSCRMatchSetArray
#  to set the array values and call the users callback routine.
# APSSCRMakePrefLBoxTextLine : used by APSListSCRMatchSetArray to make the listbox text
#  for preferred/reference snapshots.  The regular snapshot list must be made first.
# APSSCRMakeLBoxTextLine : makes listbox text lines for APSListSCRMatch
# APSFileLineCount : counts the number of lines in a file.
# APSSCRFilterFileCallback : used by filter-file selection listbox for handling user's
#  filter file choices.
# APSSCRPVFilterWidget : creates widgets for specifying filtering of PVs.  This includes
#  category and beamline selection, a wildcard string, and (in some cases) filter file
#  selections.
# APSSCRDateTimeEntryWidget : make widget for date/time entry for start and end of
#   region on interest
# APSSCRResolvePreferredFileChoice : accepts a preferred file choice and system name and
#   returns the snapshot name
# APSSCRGetPreferredChoiceList : returns the list of preferred files available for a system,
#   for display to the user.
# APSSCRGetPreferredDataLists : returns the list of preferred files, suffixes, and allowed
#   users for a system.
# APSSCRInstallPreferredFile : installs a snapshot as a specific preferred file.
# APSSCRAlterSnapshot : alters a snapshot by replacing data with data from a second
#  file.  The second file has the same columns as a snapshot. 
# APSAlterSCRRequestFile : changes a request file using data from other files to
#  indicate what to remove, add, rename, or replace.
# APSAddToSCRRequestFile : adds data to a request file.
# APSRemoveFromSCRRequestFile : removes data from a request file.
# APSRenameInSCRRequestFile : renames data in a request file.  I.e., it changes the
#  ControlNames.
# APSMakeSCRCategoryFiles : takes a request file and makes another SDDS file
#  containing one row for each unique value of a specified column.
#  Used for making category and beamline lists after creating a
#  new request file.
# APSScaleSnapshot : scales the numerical values in a snapshot by a specified
#  factor.

set SCRdebug 0

# This procedure must be called.  The procedure is in SiteSpecificSCR.tcl
# and is responsible for checking for SCR directories and setting up
# global variables for other SCR procedures.

if {(![info exists apsSkipSCRDefineVariables]) || ($apsSkipSCRDefineVariables == 0)} {
    if {[catch {APSSCRDefineVariables} apsSCRDefineVariablesResults]} {
        global tk_version
        if {[info exists tk_version]} {
            update
            APSAlertBox .errorMessage -errorMessage "Error (APSSCRDefineVariables): $apsSCRDefineVariablesResults"
            exit
        } else {
            puts stderr "Error (APSSCRDefineVariables): $apsSCRDefineVariablesResults"
            exit
        }
    }
}

proc APSSCRLogAction {args} {
    set file ?
    set action ?
    set script ?
    global apsScriptCommand apsSCRInhibitActionLogging
    if $apsSCRInhibitActionLogging return
    if [info exists apsScriptCommand] {
        set script $apsScriptCommand
    }
    if [APSStrictParseArguments {file action script}]==-1 {
        return -code error "APSSCRLogAction: invalid arguments"
    }
    if [catch {exec whoami} user] {
        set user ?
    }
    if [catch {exec uname -snr} host] {
        set host ?
    }
    if [catch {exec logMessage -sourceId=SCRAudit \
                 -tag=User $user \
                 -tag=Host $host \
                 -tag=File $file \
                 -tag=Action $action \
                 -tag=Script $script} result] {
        return -code error $result
    }
}

proc APSSCRGetUBOPLink {args} {
    global apsSCRSystem apsSCRUbopLink
    if {$apsSCRSystem=="SR"} {
	set oldDir [pwd]
	cd /home/helios/oagData/SCR/snapshots/SR
	set link [file tail [file readlink SR-UserBeamPreferred.gz]]
	switch $link {
	    SR-UserBeamPreferredNoP0Feedback.gz {
		set apsSCRUbopLink "without P0 feedback"
	    }
	    SR-UserBeamPreferredWithP0Feedback.gz {
		set apsSCRUbopLink "with P0 feedback"
	    }
	    default {
		return -code error "Invalid SR UBOP link -- $link"
	    }
	}
    } else {
	set apsSCRUbopLink ""
    }
}

proc APSSCRSystemChoiceWidget {widget args} {
    global apsSCRSystemList apsSCRSystem apsSCRUbopLink
    set parent .
    set menuParent .menu
    set variable apsSCRSystem
    set command ""
    set mode menu
    APSStrictParseArguments {parent variable command menuParent mode}

    if [string compare $mode buttons]==0 {
        set w0 $parent$widget
        APSFrameGrid $widget -parent $parent -xList {x1 x2} \
          -contextHelp "Selects SCR system"

        global $variable
        set index 0
        foreach system [lsort $apsSCRSystemList] {
            if [expr $index%16]==0 {
                set w $w0.x1.f$index
                frame $w
                pack $w -side top
            }
            incr index
            APSButton .b$system -parent $w -text $system -size small\
              -command "set $variable $system ; $command" \
              -contextHelp "Changes system choice to $system"
        }
        APSLabeledOutput .current -parent $w0.x2 -label "Current choice: " \
          -textVariable $variable -width 14
    } else {
        APSLabeledOutput $widget -parent $parent -label "Current system choice: " \
          -textVariable $variable -width 14 \
          -contextHelp "A system is the basic unit SCR deals with. Save files are made and accessed for individual systems only.  While you may save several systems in sequence using a group save, these systems are saved in an independent fashion in separate files."
#	APSSCRGetUBOPLink
	#APSLabeledOutput ${widget}1 -parent $parent -label "UBOP link to" \
	 \#   -textVariable apsSCRUbopLink -width 40 \
	   \# -contextHelp "display the SR UBOP link, blank for other system"
#	APSButton .get -parent $parent${widget}1 -size small -text "Get" -packOption "-side right" -command "APSSCRGetUBOPLink"
        APSMenubarAddMenu .syschoice -text "System" -parent $menuParent
        set w $menuParent.syschoice.menu
        global apsSCRGroupDescription apsSCRGroupMembers apsSCRGroupMenuOrder
        set index 0
        foreach item $apsSCRGroupMenuOrder {
            incr index
            APSSCRMakeSystemSubMenu .group$index -parent $w -label $apsSCRGroupDescription($item) \
              -variable $variable -command $command -groupName $item \
              -systemList $apsSCRGroupMembers($item)
        }
    }
}

proc APSSCRMakeSystemSubMenu {widget args} {
    set parent "" 
    set label "NoLabel"
    set systemList ""
    set variable ""
    set command ""
    set groupName ""
    APSStrictParseArguments {parent label systemList variable command groupName}

    menu $parent$widget
    $parent add cascade -label $label -menu $parent$widget
    set w $parent$widget
    if ![winfo exists $w] {
        puts stderr "Not found: $w"
        return
    }
    foreach item $systemList {
        $w add command -label $item -command \
          "set $variable $item; set apsSCRGroup $groupName; $command"
    }
}

proc APSSCRSetSystemList {systemList} {
    global apsSCRSystemList apsSCRSystemFlag apsSCRSystem
    if [llength $systemList] {
        set apsSCRSystemList $systemList
        set apsSCRSystem [lindex $systemList 0]
    } else {
        APSSCRDefineVariables
    }
    foreach element $apsSCRSystemList {
        set apsSCRSystemFlag($element) 0
    }
    set apsSCRSystemFlag($apsSCRSystem) 1
    set apsSCRSystem $apsSCRSystem
}

proc APSMakeSCRMatchOptions {file args} {
    global apsSCRFilterBeamline apsSCRFilterCategory 
    global apsSCRSuppressSingleBeamline
    set includeReadOnlys 0
    set protectionLock 1
    set pvNameFilter "*"
    set manualFieldsOnly 0
    set invertChoices 0
    set noConnections 0
    APSStrictParseArguments {includeReadOnlys protectionLock pvNameFilter manualFieldsOnly \
                               invertChoices noConnections}
    set snapshotColumnList [APSGetSDDSNames -class column -fileName $file]
    if [llength $snapshotColumnList]<2 {
        return -code error "APSMakeSCRMatchOptions: Too few columns in snapshot file"
    }
    set matchOpt1 ""
    set first 1
    set count(1) [llength [array names apsSCRFilterBeamline]]
    if {$apsSCRSuppressSingleBeamline && $count(1)==1} {
        set matchOpt1 -match=column,Beamline=*
    } else {
        foreach filt [array names apsSCRFilterBeamline] {
            if {$apsSCRFilterBeamline($filt)} {
                if {$first} {
                    set matchOpt1 "-match=column,Beamline=$filt"
                    set first 0
                } else {
                    set matchOpt1 "${matchOpt1},Beamline=$filt,\\|"
                }
            }
        }
    }
    set matchOpt2 ""
    set first 1
    set count(2) [llength [array names apsSCRFilterCategory]]
    foreach filt [array names apsSCRFilterCategory] {
        if {$apsSCRFilterCategory($filt)} {
            if {$first} {
                set matchOpt2 "-match=column,Category=$filt"
                set first 0
            } else {
                set matchOpt2 "${matchOpt2},Category=$filt,\\|"
            }
        }
    }
    set matchOpt3 ""
    if {$pvNameFilter != "*"} {
        set count(3) 2
        set first 1
        foreach pvNameFilter0 [split $pvNameFilter ,] {
            regsub -all {\[} "$pvNameFilter0" {\[} name1
            regsub -all {\]} "$name1" {\]} name2
            if $first {
                set matchOpt3  "-match=column,ControlName=${name2}"
            } else {
                append matchOpt3 ,ControlName=$name2,|
            }
            set first 0
        }
    } else {
        set count(3) 1
    }

    set matchOpt4 ""
    if {$protectionLock && [lsearch -exact $snapshotColumnList IsProtected]!=-1} {
        set matchOpt4 "-match=column,IsProtected=+n"
    }

    set matchOpt5 ""
    if {!$includeReadOnlys && [lsearch -exact $snapshotColumnList IsReadOnly]!=-1} {
        set matchOpt5 "-match=column,IsReadOnly=+n"
    }

    set matchOpt6 ""
    if {$manualFieldsOnly && [lsearch -exact $snapshotColumnList OpsIntervention]!=-1} {
        set matchOpt6 "-match=column,OpsIntervention=+y"
    }

    if $invertChoices {
        # don't invert protection, read-only, or manual only
        foreach opt {1 2 3} {
            if {$count($opt)>1 && [string length [set matchOpt$opt]]} {
                append matchOpt$opt ,!
            }
        }
    }
    set matchOpt7 ""
    if $noConnections {
        set matchOpt7 "-match=col,ValueString=_NoConnection_"
    }
    return -code ok "$matchOpt1 $matchOpt2 $matchOpt3 $matchOpt4 $matchOpt5 $matchOpt6 $matchOpt7"
}

proc APSCheckSCRSelections {} {
    global apsSCRFilterBeamline apsSCRFilterCategory apsSCRSuppressSingleBeamline
    set beamline 0
    if {$apsSCRSuppressSingleBeamline && [llength [array names apsSCRFilterBeamline]]==1} {
        set beamline 1
    } else {
        if {[info exists apsSCRFilterBeamline]} {
            foreach filt [array names apsSCRFilterBeamline] {
                if [subst \$apsSCRFilterBeamline($filt)] {
                    set beamline 1
                    break
                }
            }       
        }
    }
    if !$beamline {
        return 0
    }
    if {[info exists apsSCRFilterCategory]} {
        foreach filt [array names apsSCRFilterCategory] {
            if [subst \$apsSCRFilterCategory($filt)] {
                return 1
            }
        }
    }
    return 0
}

proc APSClearSCRFilterFlags {} {
    global apsSCRFilterBeamline apsSCRFilterCategory apsSCRFilterFileName apsSCRPVNameFilter
    global apsSCRInvertFilterFile apsSCRFilterFileDescription apsSCRPVSetpointFilter
    if {[info exists apsSCRFilterBeamline]} {
        foreach filt [array names apsSCRFilterBeamline] {
            set apsSCRFilterBeamline($filt) 0
        }       
    }
    if {[info exists apsSCRFilterCategory]} {
        foreach filt [array names apsSCRFilterCategory] {
            set apsSCRFilterCategory($filt) 0
        }
    }
    set apsSCRPVNameFilter "*"
    set apsSCRPVSetpointFilter 0
    set apsSCRFilterFileName ""
    set apsSCRFilterFileDescription ""
    set apsSCRInvertFilterFile 0
}

proc APSSCRMakeSnapshotFilename {args} {
    global apsSCRRequestDir apsSCRSnapDir apsSCRSystem
    set machine ""
    if [APSStrictParseArguments {machine}] {
        return -code error "APSSCRMakeSnapshotFilename: bad argument"
    }

    if [string length $machine]==0 {
        set machine $apsSCRSystem
    }

    if ![file exists [file join $apsSCRRequestDir $machine.req]] {
        return -code error "Bad machine name $machine" 
    }

    set oldDir [pwd]
    cd [file join $apsSCRSnapDir $machine]

    catch {exec timeconvert -now} timeList 
    set dt \
      [format %4ld-%03ld-%02ld%02ld-%02ld%02ld%02ld \
         [lindex $timeList 0] [lindex $timeList 1] \
         [lindex $timeList 2] [lindex $timeList 3] \
         [lindex $timeList 5] [lindex $timeList 6] \
         [lindex $timeList 7] ]
    while 1 {
        set snapshot ${machine}${dt}
        if {[file exists $snapshot] || [file exists $snapshot.gz]} {
            catch {exec timeconvert -now} timeList 
            set dt \
              [format %4ld-%03ld-%02ld%02ld-%02ld%02ld%02ld \
                 [lindex $timeList 0] [lindex $timeList 1] \
                 [lindex $timeList 2] [lindex $timeList 3] \
                 [lindex $timeList 5] [lindex $timeList 6] \
                 [lindex $timeList 7] ]
        } else {
            cd $oldDir
            return [concat $snapshot $timeList]
        }
    }
}

proc APSSaveMachine {args} {
    global apsSCRRequestDir apsSCRSnapDir SCRdebug 
    global apsSCRUseCarestore apsSCRUseGPServer apsSCRUseCASRServer apsUpgrade
    set machine ""
    set description ""
    set snapshot ""
    set temporary 0
    set requestFile ""
    set statusCallback "APSNoOp"
    set routine 0
    set pendIOTime 80
    set pingTimeOut 80
    if [APSStrictParseArguments {machine description snapshot temporary requestFile \
                                   statusCallback routine pendIOTime pingTimeOut }] {
        return -code error "APSSaveMachine: bad arguments"
    }
    if $apsSCRUseCASRServer {
        if [catch {APSSaveMachineByCasr -machine $machine -description $description \
                     -pendIOTime $pendIOTime -pingTimeOut $pingTimeOut \
                     -snapshot $snapshot -temporary $temporary -requestFile $requestFile \
                     -statusCallback $statusCallback -routine $routine} result] {
            return -code error "APSSaveMachine: $result"
        }
        return $result
    }
    if ![file exists [file join $apsSCRRequestDir $machine.req]] {
        return -code error "APSSaveMachine: Bad machine name $machine" 
    }

    set oldDir [pwd]
    cd [file join $apsSCRSnapDir $machine]

    if $SCRdebug {
        puts stderr "Directory changed to [pwd]"
    }

    if {[string length $snapshot]==0} {
        set snapshot [APSSCRMakeSnapshotFilename -machine $machine]
        set timeList [lrange $snapshot 1 end]
        set snapshot [lindex $snapshot 0]
    } else {
        catch {exec timeconvert -now} timeList 
    }
    
    if [string length $description] {
        set description [APSMakeSafeQualifierString $description]
    }

    if [string length $requestFile]==0 {
        set requestFile ${apsSCRRequestDir}/${machine}.req 
    }
    if ![file exists $requestFile] {
        cd $oldDir
        return -code error "APSSaveMachine: $requestFile not found" 
    }
    if $SCRdebug {
        puts stderr "Using request file $requestFile"
    }

    set logFile /tmp/[APSTmpString].burtlog
    set tmpID [APSUniqueName tmpID]
    if $temporary {
        set tmpReqFile /tmp/[APSTmpString]
        APSAddToTmpFileList -ID $tmpID -fileList $tmpReqFile
        if [string compare [file extension $requestFile] ".gz"]==0 {
            if {$apsUpgrade} {
                if [catch {exec gzip -dcn $requestFile \
                               | sddsprocess -pipe=in -match=col,ControlName=,! \
                               $tmpReqFile } result] {
                    cd $oldDir
                    return -code error "APSSaveMachine1: $result"
                }
            } else {
                if [catch {exec gzip -dcn $requestFile \
                               | sddsprocess -pipe=in -match=col,ControlName=,! \
                               -match=col,ControlType=,! $tmpReqFile } result] {
                    cd $oldDir
                    return -code error "APSSaveMachine1: $result"
                }
            }
            set requestFile $tmpReqFile
            if $SCRdebug {puts stderr "Using request file $tmpReqFile" }
        } else {
            if {$apsUpgrade} {
                if [catch {exec sddsprocess $requestFile -match=col,ControlName=,! \
                               $tmpReqFile } result] {
                    cd $oldDir
                    return -code error "APSSaveMachine2: $result"
                }
            } else {
                if [catch {exec sddsprocess $requestFile -match=col,ControlName=,! \
                               -match=col,ControlType=,! $tmpReqFile } result] {
                    cd $oldDir
                    return -code error "APSSaveMachine2: $result"
                }
            }
        }
        if $apsSCRUseGPServer {
            if $SCRdebug {
                puts stderr "Using GPServer to take snapshot."
                puts stderr "request file: $tmpReqFile [exec sddscheck $tmpReqFile]"
            }
            catch {exec tclcaGPServer &}
            after 1000
            if {$apsUpgrade} {
                if [catch {exec tclcaget -requestFile $tmpReqFile -pipe out \
                               -logFile $logFile.sdds \
                               | sddsprocess -pipe=in $snapshot \
                               "-redefine=column,Count,1,type=long" \
                               "-reprint=column,Lineage,-" \
                               "-reprint=column,BackupMsg,-" \
                               "-reprint=column,RestoreMsg,-" \
                               "-reprint=column,ControlMode,-" } result] {
                    if $SCRdebug { puts stderr "Error from GPServer: $result" }
                }
            } else {
                if [catch {exec tclcaget -requestFile $tmpReqFile -pipe out \
                               -logFile $logFile.sdds \
                               | sddsprocess -pipe=in $snapshot \
                               "-redefine=column,Count,1,type=long" \
                               "-reprint=column,Lineage,-" \
                               "-reprint=column,BackupMsg,-" \
                               "-reprint=column,RestoreMsg,-" \
                               "-reprint=column,ControlType,pv" \
                               "-reprint=column,ControlMode,-" } result] {
                    if $SCRdebug { puts stderr "Error from GPServer: $result" }
                }
            }
            if {[file exists $logFile.sdds] && \
                  [string compare [exec sdds2stream -rows $logFile.sdds] "0 rows"]!=0} {
                catch {exec sddsprintout $logFile.sdds $logFile -column}
            } 
            set logFile $apsSCRSnapDir/$machine/$logFile
            # catch {file delete $logFile.sdds}
        } else {
            if {$apsUpgrade} {
                if {$SCRdebug} {
                    puts stderr "Using sddspvasaverestore to take snapshot."
                }
                if [catch {exec sddspvasaverestore -save $tmpReqFile $snapshot "-description=$description" \
                             -pend=$pendIOTime -logFile=$logFile -waveform=onefile,savewaveform } result] {
                    if {$SCRdebug} {
                        puts stderr "Error from sddspvasaverestore: $result"
                    }
                    cd $oldDir
	            return -code error "Error from sddspvasaverestore save $tmpReqFile: \n$result"
                }
            } else {
                if {$SCRdebug} {
                    puts stderr "Using sddscasr to take snapshot."
                }
                if [catch {exec sddscasr -save $tmpReqFile $snapshot "-description=$description" \
                             -pend=$pendIOTime -logFile=$logFile -waveform=onefile,savewaveform } result] {
                    if {$SCRdebug} {
                        puts stderr "Error from sddscasr: $result"
                    }
                    cd $oldDir
	            return -code error "Error from sddscasr save $tmpReqFile: \n$result"
                }
            }
        }
        if ![file exists $snapshot] {
            cd $oldDir
            APSDeleteTmpFileList -ID $tmpID
            return -code error "APSSaveMachine: $snapshot does not exist, snapshot taken failed." 
        }
        file delete $tmpReqFile
        if $SCRdebug {
            puts stderr "Snapshot is $snapshot: [exec sdds2stream -rows $snapshot]"
        }
        if [catch {APSSCRCheckSnapshot -snapshot $snapshot -logFile $logFile \
                     -GPServer $apsSCRUseGPServer} result] {
            APSDeleteTmpFileList -ID $tmpID
            cd $oldDir
            return -code error "APSSaveMachine: APSSaveMachine: $result"
        }
        if {[file exists $logFile]} {
            if ![file size $logFile] {
                file delete $logFile
            } else {
                cd $oldDir
                return -code ok [list $snapshot $logFile]
            }
        }
        catch {file delete ${snapshot}~}
        APSDeleteTmpFileList -ID $tmpID
        cd $oldDir
        return -code ok $snapshot
    } else {
        # permanent snapshot
        if $SCRdebug {
            puts stderr "Doing save with [file tail $requestFile] to $snapshot"
        }
        if [string length $statusCallback] {
            eval $statusCallback {"Doing save with [file tail $requestFile] to $snapshot"}
        }
        set time0 [format %.0lf [lindex $timeList 8]]
        if $apsSCRUseGPServer {
            if $SCRdebug {
                puts stderr "Using GPServer to take snapshot."
                puts stderr "request file: $requestFile [exec sddscheck $requestFile]"
            }
            catch {exec tclcaGPServer &}
            set logFile $snapshot.log 
            if $SCRdebug {
                puts stderr "Snapshot file is $snapshot"
                puts stderr "Logfile is $logFile"
                puts stderr "PWD is [pwd]"
            }
            if {$apsUpgrade} {
                if [catch {exec tclcaget -requestFile $requestFile \
                               -logFile $logFile.sdds -pipe out \
                               | sddsprocess -pipe=in $snapshot \
                               "-redefine=column,Count,1,type=long" \
                               "-reprint=column,Lineage,-" \
                               "-reprint=column,BackupMsg,-" \
                               "-reprint=column,RestoreMsg,-" \
                               "-reprint=column,ControlMode,-" \
                               "-reprint=parameter,SnapshotDescription,$description" \
                               "-reprint=parameter,RequestFile,$requestFile" \
                               "-reprint=parameter,SnapshotFilename,${snapshot}"} result] {
                    if $SCRdebug { puts stderr "Error from GPServer: $result" }
                }
            } else {
                if [catch {exec tclcaget -requestFile $requestFile \
                               -logFile $logFile.sdds -pipe out \
                               | sddsprocess -pipe=in $snapshot \
                               "-redefine=column,Count,1,type=long" \
                               "-reprint=column,Lineage,-" \
                               "-reprint=column,BackupMsg,-" \
                               "-reprint=column,RestoreMsg,-" \
                               "-reprint=column,ControlType,pv" \
                               "-reprint=column,ControlMode,-" \
                               "-reprint=parameter,SnapshotDescription,$description" \
                               "-reprint=parameter,RequestFile,$requestFile" \
                               "-reprint=parameter,SnapshotFilename,${snapshot}"} result] {
                    if $SCRdebug { puts stderr "Error from GPServer: $result" }
                }
            }
            if {[file exists $logFile.sdds] && \
                  [string compare [exec sdds2stream -rows $logFile.sdds] "0 rows"]!=0} {
                catch {exec sddsprintout $logFile.sdds $logFile -column}
            }
            set logFile $apsSCRSnapDir/$machine/$logFile
            # catch {file delete $logFile.sdds}
        } else {
            if {$apsUpgrade} {
                if [catch {exec sddspvasaverestore $requestFile $snapshot -save -pend=$pendIOTime \
                             -logFile=$logFile "-description=$description" -waveform=onefile,savewaveform } result] {
                    if {$SCRdebug} {
                        puts stderr "Problem doing sddspvasaverestore: $result"
                    }
                    cd $oldDir
                    return -code error "Problem doing sddspvasaverestore save $requestFile: \n$result"
                }
            } else {
                if [catch {exec sddscasr $requestFile $snapshot -save -pend=$pendIOTime \
                             -logFile=$logFile "-description=$description" -waveform=onefile,savewaveform } result] {
                    if {$SCRdebug} {
                        puts stderr "Problem doing sddscasr: $result"
                    }
                    cd $oldDir
                    return -code error "Problem doing sddscasr save $requestFile: \n$result"
                }
            }
            if $SCRdebug {
                if ![file exists $snapshot] {
                    puts stderr "Snapshot should be $snapshot--NOT FOUND"
                } else {
                    puts stderr "$snapshot was found."
                }
            }
        }
        catch {file delete ${snapshot}~}
        if ![file exists $snapshot] {
            cd $oldDir
            APSDeleteTmpFileList -ID $tmpID
            return -code error "APSSaveMachine: $result" 
        }
        if $SCRdebug {
            puts stderr "Snapshot taken."
        }
        if [string length $statusCallback] {
            eval $statusCallback {"Snapshot taken."}
        }
        set action "save"
        if $routine {
            set action "save-routine"
        }
        if {[catch {APSSCRLogAction -file $snapshot -action $action} result] && \
              [string length $statusCallback]} {
            eval $statusCallback {"$result"}
        }
    }
    if $SCRdebug {
        puts stderr "Snapshot is $snapshot: [exec sdds2stream -rows $snapshot]"
    }
    if [catch {APSSCRProcessSnapshot -snapshot $snapshot -statusCallback $statusCallback \
                 -logFile $logFile \
                 -requestFile $requestFile} result] {
        cd $oldDir
        return -code error "APSSaveMachine: $result"
    }
    cd $oldDir
    return $result
}

#this procedure is to replace APSSaveMachine for saving snapshot
proc APSSaveMachineByCasr {args} {
    global apsSCRRequestDir apsSCRSnapDir SCRdebug 
    global apsSCRUseCASRServer apsSCRFirstSnapshot apsSCRSnapshotLogFile
    set machine ""
    set description ""
    set snapshot ""
    set temporary 0
    set requestFile ""
    set statusCallback "APSNoOp"
    set routine 0
    set pendIOTime 80
    set pingTimeOut 70
    if [APSStrictParseArguments {machine description snapshot temporary requestFile \
                                   statusCallback routine \
                                   pendIOTime pingTimeOut}] {
        return -code error "APSSaveMachineByCasr1: bad arguments"
    }
    if ![file exists [file join $apsSCRRequestDir $machine.req]] {
        return -code error "APSSaveMachineByCasr2: Bad machine name $machine" 
    }
    if ![string length $requestFile] {
        set requestFile ${apsSCRRequestDir}/${machine}.req 
    }
    if {$machine=="XrayTranslation"} {
        set sys XrayTr
    } else {
        set sys $machine
    } 
    if $apsSCRUseCASRServer {
        if [catch {APScagetTextFromWaveform -pvName ${sys}:ScrFilenameWF } initialOutput] {
            return -code error "APSSaveMachineByCasr3: unable to get the output file PV - ${sys}:ScrFilenameWF: $initialOutput"
        }
        if [catch {APScaputTextToWaveform -pvName ${sys}:ScrFilenameWF -text ""} result] {
            return -code error "APSSaveMachineByCasr4: unable to set the output file PV - ${sys}:ScrFilenameWF: $result"
        }
        #check casr RunControl for using casr server 
        if [catch {APSSCRCheckCasrRunControl -machine $machine -initialOutput $initialOutput} result] {
            return -code error "APSSaveMachineByCasr5:  $result"
        }
    }
    #setup the description pv and inputfile pv, clear the output file pv
    #initialize the output file PV
    
    if [catch {APScaputTextToWaveform -pvName ${sys}:ScrDescriptWF -text $description} result] {
        return -code error "APSSaveMachineByCasr6: unable to set the description PV- ${sys}:ScrDescriptWF: $result"
    }
    if [catch {APScaputTextToWaveform -pvName ${sys}:ScrInputFileWF -text $requestFile} result] {
        return -code error "APSSaveMachineByCasr7: unable to set the inputfile PV- ${sys}:ScrInputFileWF: $result"
    }
    if !$apsSCRUseCASRServer {
        #one time saving
        if [catch {APSCASRSaveMachine -machine $machine -pendIOTime $pendIOTime -pingTimeOut $pingTimeOut \
                     -routine $routine -temporary $temporary -snapshot $snapshot \
                     -statusCallback $statusCallback } result] {
            return -code error "APSSaveMachineByCasr8: $result"
        }
        return $result
    }
    #saving with daemon mode 
    
    if $apsSCRFirstSnapshot($machine) {
        exec medm -x -attach -macro RCPV=${sys}:ScrRunControlRC ./sr/psApp/APSRunControlSingle.adl &
        if [catch {APSCASRSaveMachine -machine $machine -snapshot $snapshot \
                     -statusCallback $statusCallback -daemon 1 -temporary $temporary \
                     -pendIOTime $pendIOTime -pingTimeOut $pingTimeOut \
                     -logFile $apsSCRSnapshotLogFile($machine) } result] {
            if [catch {APSSCRAbortCASRRunControl -machine $machine} result1] {
                return -code error "APSSaveMachineByCasr9:  $result1"
            }
            return -code error "APSSaveMachineByCasr:  $result"
        }
       # puts "first snapshot..."
        set apsFirstSnapshot($machine) 0
    } else {
        if [file exist $apsSCRSnapshotLogFile($machine)] {
            exec rm $apsSCRSnapshotLogFile($machine)
        }
        if [catch {exec cavget -list=${sys}:ScrRunControlRC.RUN -pend=10} running] {
            return -code error  "APSSaveMachineByCasr10: can not read ${sys}:ScrRunControlRC.RUN : $running"
        }
        if {$running=="?"} {
            return -code error "APSSaveMachineByCasr11: channel access error for ${sys}:ScrRunControlRC"
        }
        if !$running {
            return -code error "APSSaveMachineByCasr12: $sys:ScrRunControl is not running!"
        }
        if [catch {exec cavput -list=${sys}:ScrSaveSignalBO=1 -pend=10} result] {
            #abort sddscasr
            if [catch {APSSCRAbortCASRRunControl -machine $machine} result1] {
                return -code error "APSSaveMachineByCasr13:  $result1"
            }
            return -code error "APSSaveMachineByCasr14: $result"
        }
    }
    set error 1
    set timeout [expr [clock seconds] + $pendIOTime]
    while {[clock seconds]<$timeout} {
        if [catch {exec cavget -list=${sys}:ScrRunControlRC.RUN -pend=10} running] { 
            return -code error "APSSaveMachineByCasr15: $running"
        }
        if [catch {APScagetTextFromWaveform -pvName ${sys}:ScrFilenameWF} outputFile] { 
            return -code error "APSSaveMachineByCasr16: $outputFile"
        }
        if {$running=="?"} {
            return -code error "APSSaveMachineByCasr17: channel access error of ${sys}:ScrRunControlRC.RUN"
        }
        if [catch {exec cavget -list=${sys}:ScrSaveSignalBO -pend=10 -num} done] {
            return -code error "APSSaveMachineByCasr18: $done"
        }
        if {!$done && [string length $outputFile] } {
            set error 0
            break
        }
        after 500
    }
    # puts "output file: $outputFile"
    if $error {
        if [file exist $apsSCRSnapshotLogFile($machine)] {
            APSFileDisplayWindow [APSUniqueName .] \
                -fileName  $apsSCRSnapshotLogFile($machine) -deleteOnClose 1 -width 100 \
                -comment "Log file for $machine" \
                -printCommand "enscript -r"
        }
        return -code error "APSSaveMachineByCasr19: Run time out for sddscasr. Snapshot taking failed (the server maybe busy!)"
    }
    set dir [file dirname $outputFile] 
    #puts stderr "outputfile: $outputFile, [string length $outputFile]"
    if {$dir=="."} {
        set snapshot $apsSCRSnapDir/$machine/$outputFile
    } else {
        set snapshot $outputFile
    }
    
    if ![file exists $snapshot] {
        # wait up to 10 seconds for the file system updating the new file
        set count 0
        while {$count<20} {
            if ![file exists $snapshot] {
                after 1000
            } else {
                break
            }
            incr count
        }
       # puts $snapshot
        if ![file exists $snapshot] {
            return -code error "APSSaveMachineByCasr20: \n\n$snapshot does not exist,\n the server may have been busy; Please make another save."
        }
    }
    if [string length $statusCallback] {
        eval $statusCallback {"Snapshot taken"}
    }
    if $temporary {
        set fileList [glob -nocomplain ${snapshot}*]
        APSAddToTmpFileList -ID tempSave -fileList $fileList
        set logFile $apsSCRSnapshotLogFile($machine)
        
        if [catch {APSSCRCheckCASRSnapshot -snapshot $snapshot -logFile $logFile } result] {
            APSDeleteTmpFileList -ID $tmpID
            return -code error "APSSaveMachineByCasr21: $result"
        }
        if {[file exists $logFile]} {
            if ![file size $logFile] {
                file delete $logFile
            } else {
                return -code ok [list $snapshot $logFile ]
            }
        }
        catch {file delete ${snapshot}~}
        return -code ok $snapshot
    }
    if [catch {APScagetTextFromWaveform -pvName ${sys}:ScrInputFileWF} requestFile] { 
        return -code error "APSSaveMachineByCasr22: $requestFile"
    }
    if [catch {APSSCRProcessSnapshot -snapshot $snapshot -statusCallback $statusCallback \
                 -logFile $apsSCRSnapshotLogFile($machine) \
                 -requestFile $requestFile} result] {
        return -code error "APSSaveMachineByCasr23: $result"
    }
    return $result
}

#this procedure is for saving snapshot one time or the first time for using sddscasr server 
proc APSCASRSaveMachine {args} {
    global apsSCRRequestDir apsSCRSnapDir SCRdebug 
    global apsSCRUseCASRServer apsUpgrade
    set machine ""
    set snapshot ""
    set temporary 0
    set statusCallback "APSNoOp"
    set routine 0
    set pendIOTime 80
    set pingTimeOut 70
    set logFile ""
    set daemon 0
    if [APSStrictParseArguments {machine snapshot temporary \
                                   statusCallback routine \
                                   pendIOTime pingTimeOut logFile daemon }] {
        return -code error "APSSaveMachine: bad arguments"
    }
    
    set oldDir [pwd]
    cd [file join $apsSCRSnapDir $machine]

    if $SCRdebug {
        puts stderr "Directory changed to [pwd]"
    }
    if {[string length $snapshot]==0} {
        set snapshot [APSSCRMakeSnapshotFilename -machine $machine]
        set timeList [lrange $snapshot 1 end]
        set snapshot [lindex $snapshot 0]
    } else {
        catch {exec timeconvert -now} timeList 
    }
    if ![string length $logFile] {
        set logFile /tmp/[APSTmpString].burtlog
    }
    if {$machine=="XrayTranslation" } {
        set sys XrayTr
    } else {set sys $machine}
    set tmpID [APSUniqueName tmpID]
    if [catch {APScagetTextFromWaveform -pvName ${sys}:ScrInputFileWF} requestFile] {
        return -code error "APSCASRSaveMachine: unable to get the inputfile PV- ${sys}:ScrInputFileWF: $requestFile"
    }
    if [catch {APScaputTextToWaveform -pvName ${sys}:ScrFilenameWF -text ""} result] {
        return -code error "APSCASRSaveMachine: unable to set the outputfile PV - ${sys}:ScrFilenameWF: $result"
    }
    if $temporary {
        if $daemon {
            if $SCRdebug {
                puts stderr "Using sddscasr daemon mode to take snapshot."
            }
            set snapshot $machine
            set dir $apsSCRSnapDir/$machine
            if {$apsUpgrade} {
                if [catch {exec sddspvasaverestore -pend=$pendIOTime $requestFile -waveform=onefile,savewaveform \
                             -save -inputFilePV=${sys}:ScrInputFileWF,ca \
                             $snapshot -dailyFiles -triggerPV=${sys}:ScrSaveSignalBO,ca \
                             -daemon -runControlPV=string=${sys}:ScrRunControlRC,pingTimeOut=$pingTimeOut \
                             "-runControlDesc=string=take snapshot" \
                             "-description=pv=${sys}:ScrDescriptWF,provider=ca" -logFile=$logFile \
                             -outputFilePV=${sys}:ScrFilenameWF,ca & } result] {
                    return -code error "APSCASRSaveMachine: $result"
                }
            } else {
                if [catch {exec sddscasr -pend=$pendIOTime $requestFile -waveform=onefile,savewaveform \
                             -save -inputFilePV=${sys}:ScrInputFileWF \
                             $snapshot -dailyFiles -casavePV=${sys}:ScrSaveSignalBO \
                             -daemon -runControlPV=string=${sys}:ScrRunControlRC,pingTimeOut=$pingTimeOut \
                             "-runControlDesc=string=take snapshot" \
                             "-description=pv=${sys}:ScrDescriptWF" -logFile=$logFile \
                             -outputFilePV=${sys}:ScrFilenameWF & } result] {
                    return -code error "APSCASRSaveMachine: $result"
                }
            }
            after 10000
            if [catch {exec cavget -list=${sys}:ScrRunControlRC.RUN -pend=10} run] {
                return -code error "APSCASRSaveMachine: $run"
            }
            if {$run=="?"} {
                return -code error "APSCASRSaveMachine: channel access error for ${sys}:ScrRunControlRC"
            }
            if !$run {
                return -code error "APSCASRSaveMachine: ${sys}:RunControlRC is not running, problem with taking snapshot!"
            }
            cd $oldDir
            return daemon
        } else {
            if $SCRdebug {puts stderr "Using sddscasr to take snapshot."}
            if {$apsUpgrade} {
                if [catch {exec sddspvasaverestore  $requestFile -waveform=onefile,savewaveform \
                             -pend=$pendIOTime -save -inputFilePV=${sys}:ScrInputFileWF,ca $snapshot \
                             -logFile=$logFile -description=pv=${sys}:ScrDescriptWF,ca} result] {
                    return -code error "APSCASRSaveMachine save $requestFile: \n$result"
                }
            } else {
                if [catch {exec sddscasr  $requestFile -waveform=onefile,savewaveform \
                             -pend=$pendIOTime -save -inputFilePV=${sys}:ScrInputFileWF $snapshot \
                             -logFile=$logFile -description=pv=${sys}:ScrDescriptWF} result] {
                    return -code error "APSCASRSaveMachine save $requestFile: \n$result"
                }
            }
        }
        if ![file exists $snapshot] {
            cd $oldDir
            APSDeleteTmpFileList -ID $tmpID
            return -code error "APSCASRSaveMachine: $result" 
        }
        if $SCRdebug {
            puts stderr "Snapshot is $snapshot: [exec sdds2stream -rows $snapshot]"
        }
        if [catch {APSSCRCheckCASRSnapshot -snapshot $snapshot -logFile $logFile } result] {
            APSDeleteTmpFileList -ID $tmpID
            return -code error "APSCASRSaveMachine: APSSaveMachine: $result"
        }
        if {[file exists $logFile]} {
            if ![file size $logFile] {
                file delete $logFile
            } else {
                return -code ok [list $snapshot $logFile]
            }
        }
        catch {file delete ${snapshot}~}
        APSDeleteTmpFileList -ID $tmpID
        return -code ok $snapshot
    } else {
        # permanent snapshot
        if $SCRdebug {
            puts stderr "Doing save for $machine system to $snapshot"
        }
        if [string length $statusCallback] {
            eval $statusCallback {"Doing save with $machine.req to $snapshot"}
        }
        set time0 [format %.0lf [lindex $timeList 8]]
        if $daemon {
            if $SCRdebug {
                puts stderr "Using sddscasr daemon mode to take snapshot."
            }
            set dirname [file dirname $snapshot]
            if {[string compare $dirname "."]==0} {
                set snapshot $machine
                set dir $apsSCRSnapDir
            } else {
                set snapshot $dirname/$machine
                set dir $dirname
            }
            if $SCRdebug {
                puts stderr "Logfile is $logFile"
                puts stderr "PWD is [pwd]"
            }
            if {$apsUpgrade} {
                if [catch {exec sddspvasaverestore $requestFile \
                             -pend=$pendIOTime \
                             -save -inputFilePV=${sys}:ScrInputFileWF,ca -waveform=onefile,savewaveform \
                             $snapshot -dailyFiles -triggerPV=${sys}:ScrSaveSignalBO,ca \
                             -daemon -runControlPV=string=${sys}:ScrRunControlRC,pingTimeOut=$pingTimeOut \
                             "-runControlDesc=string=take snapshot"  \
                             "-description=pv=${sys}:ScrDescriptWF,provider=ca" -logFile=$logFile  \
                             -outputFilePV=${sys}:ScrFilenameWF,ca &
                    after 1000 } result] {
                    return -code error "APSCASRSaveMachine: $result"
                }
            } else {
                if [catch {exec sddscasr $requestFile \
                             -pend=$pendIOTime \
                             -save -inputFilePV=${sys}:ScrInputFileWF -waveform=onefile,savewaveform \
                             $snapshot -dailyFiles -casavePV=${sys}:ScrSaveSignalBO \
                             -daemon -runControlPV=string=${sys}:ScrRunControlRC,pingTimeOut=$pingTimeOut \
                             "-runControlDesc=string=take snapshot"  \
                             "-description=pv=${sys}:ScrDescriptWF" -logFile=$logFile  \
                             -outputFilePV=${sys}:ScrFilenameWF &
                    after 1000 } result] {
                    return -code error "APSCASRSaveMachine: $result"
                }
            }
            after 10000
            if [catch {exec cavget -list=${sys}:ScrRunControlRC.RUN -pend=30} run] {
                return -code error $run
            }
            if !$run {
                return -code error "${sys}:RunControlRC is not running, problem with taking snapshot!"
            }
            cd $oldDir
            return daemon
        } else {
            if {$apsUpgrade} {
                if [catch {exec sddspvasaverestore  -waveform=rootname=$snapshot,directory=$apsSCRSnapDir \
                             -pend=$pendIOTime -save -inputFilePV=${sys}:ScrInputFileWF,ca -pipe=out \
                             "-description=pv=${sys}:ScrDescriptWF,provider=ca" -logFile=$logFile -waveform=onefile,savewaveform \
                             | sddsprocess -pipe=in $snapshot \
                             -redefine=par,Time,$time0,units=s,type=long \
                             -reprint=par,SnapshotFilename,$snapshot } result] {
                    if $SCRdebug {
                        puts stderr "Problem doing sddspvasaverestore: $result"
                    }
                    return -code error "Problem doing sddspvasaverestore save: $result"
                }
            } else {
                if [catch {exec sddscasr  -waveform=rootname=$snapshot,directory=$apsSCRSnapDir \
                             -pend=$pendIOTime -save -inputFilePV=${sys}:ScrInputFileWF -pipe=out \
                             "-description=pv=${sys}:ScrDescriptWF" -logFile=$logFile -waveform=onefile,savewaveform \
                             | sddsprocess -pipe=in $snapshot \
                             -redefine=par,Time,$time0,units=s,type=long \
                             -reprint=par,SnapshotFilename,$snapshot } result] {
                    if $SCRdebug {
                        puts stderr "Problem doing sddscasr: $result"
                    }
                    return -code error "Problem doing sddscasr save: $result"
                }
            }
            catch {file delete ${snapshot}~}
        }
        if ![file exists $snapshot] {
            cd $oldDir
            APSDeleteTmpFileList -ID $tmpID
            return -code error "APSCASRSaveMachine: $result" 
        }
        if $SCRdebug {
            puts stderr "Snapshot taken."
        }
        if [string length $statusCallback] {
            eval $statusCallback {"Snapshot taken."}
        }
        set action "save"
        if $routine {
            set action "save-routine"
        }
        if {[catch {APSSCRLogAction -file $snapshot -action $action} result] && \
              [string length $statusCallback]} {
            eval $statusCallback {"$result"}
        }
    }
    
    if [catch {APSSCRProcessSnapshot -snapshot $snapshot -statusCallback $statusCallback \
                 -logFile $logFile \
                 -requestFile $requestFile} result] {
        return -code error $result
    }
    return $result
}

proc APSSCRCheckCasrRunControl {args} {
    set machine ""
    set initialOutput ""
    APSStrictParseArguments {initialOutput machine}
    
    global apsSCRFirstSnapshot apsSCRSnapshotLogFile
    if {$machine=="XrayTranslation"} {
        set sys XrayTr
    } else {
        set sys $machine
    }
    
    if [catch {exec cavget -list=${sys}:ScrRunControlRC.RUN -pend=10} running] {
        return -code error "${sys}:ScrRunControlRC.RUN: $running"
    }
    if {$running=="?"} {
        return -code error "APSSCRCheckCasrRunControl: Channel access error for ${sys}:ScrRunControlRC.RUN"
    }
    
    if ![info exist apsSCRSnapshotLogFile($machine)] {
        set apsSCRSnapshotLogFile($machine) /tmp/[APSTmpString].${machine}.log
    }
    if $running {
        set apsSCRFirstSnapshot($machine) 0
        if [catch {exec cavget -list=${sys}:ScrRunControlRC.MSG -pend=10} message] {
            return -code error "APSSCRCheckCasrRunControl: $message"
        }
        if {$message=="?"} {
            return -code error "APSSCRCheckCasrRunControl: Channel access error for ${sys}:ScrRunControlRC.MSG"
        }
        if ![string match "*sleeping*" $message] {
            return -code error "APSSCRCheckCasrRunControl: sddscasr server is busy now, please wait a few seconds!"
        }
        if [catch {exec cavget -list=${sys}:ScrSaveSignalBO -pend=10 -num} taking] {
            return -code error "APSSCRCheckCasrRunControl: $taking"
        }
        if $taking {
            return -code error "APSSCRCheckCasrRunControl: sddscasr server is taking data now, please wait a few seconds!"
        }
        if [catch {APScagetTextFromWaveform -pvName ${sys}:ScrFilenameWF } output] {
            return -code error "APSSCRCheckCasrRunControl: unable to get the output file PV - ${sys}:ScrFilenameWF: $initialOutput"
        }
     #   if [string compare $initialOutput $output]!=0 {
     #       return -code error "APSSCRCheckCasrRunControl: sddscasr server is taking data now, please wait a few seconds!"
     #   }
    } else {
        set apsSCRFirstSnapshot($machine) 1
    }
}

proc APSSCRAbortCASRRunControl {args} {
    set machine ""
    set statusCallback ""
    global apsSCRFirstSnapshot
    APSStrictParseArguments {machine statusCallback}
    if {$machine=="XrayTranslation"} {
        set sys XrayTr
    } else {
        set sys $machine
    }
    if [catch {exec cavput -list=${sys}:ScrRunControlRC.ABRT=1 -pend=10} result] {
        return -code error "APSSCRAbortCASRRunControl: Unable to abort ${sys}:ScrRunControlRC: $result"
    }
    #clear record
    if [catch {APSClearRCRecord -controlPV ${sys}:ScrRunControlRC -statusCallback $statusCallback } result] {
        return -code error "APSSCRAbortCASRRunControl: ${sys}:ScrRunControlRC record is not cleared: $result; Abort failed"
    }
    if [string length $statusCallback] {
        eval $statusCallback {"${sys}:ScrRunControlRC is aborted."}
    }
    set apsSCRFirstSnapshot($machine) 1
}

proc APSSCRProcessSnapshot {args} {
    set SCRdebug 0
    set snapshot ""
    set logFile ""
    set statusCallback "APSNoOp"
    set requestFile ""
    APSStrictParseArguments {SCRdebug snapshot logFile statusCallback requestFile}
    
    if {![string length $snapshot]} {
        return -code error "APSSCRProcessSnapshot: snapshot file is not provided!"
    }
    if ![file exist $snapshot] {
        return -code error "APSSCRProcessSnapshot:snapshot $snapshot file does not exist!"
    }
    if {![string length $requestFile]} {
        return -code error "APSSCRProcessSnapshot:requestFile is not provided!"
    }
    if ![file exist $requestFile] {
        return -code error "APSSCRProcessSnapshot:requestFile $requestFile file does not exist!"
    }
    
    if $SCRdebug {
        puts stderr "Snapshot is $snapshot: [exec sdds2stream -rows $snapshot]"
    }
    set filename [file tail $snapshot]
    set pathname [file dirname $snapshot]
    
    if [string length $statusCallback] {
        eval $statusCallback {"Compressing and processing snapshot..."}
    }
    
    catch {file delete $snapshot.gz}
    if [catch {exec gzip $snapshot} result] {
        return -code error "APSSCRProcessSnapshot: error gzipping snapshot: $result"
    }
    if [file exist $snapshot.waveform] {
        if [catch {exec gzip $snapshot.waveform} result] {
            return -code error "APSSCRProcessSnapshot: error gzipping snapshot: $result"
        }
    }
#    catch {exec chmod -w [glob ${snapshot}*]}
    catch {exec chmod -w ${snapshot}.gz}
    set snapshot $snapshot.gz
    
    if $SCRdebug {puts stderr "Launching collapse process"}
    if {[file exists $pathname/collapsed]} {
        exec sddscollapse $snapshot $pathname/collapsed/c$filename &
    } else {
        exec sddscollapse $snapshot $pathname/c$filename &
    }
    
    set reqRoot $requestFile
    if {[file exists $pathname/categories]} {
        set categoryPathname $pathname/categories
    } else {
        set categoryPathname $pathname
    }
    if {![file exists ${reqRoot}.categories]  || \
          [file mtime ${reqRoot}.categories]<=[file mtime ${reqRoot}]} {
        if $SCRdebug {puts stderr "Launching category sort process"}
        exec sddssort $snapshot -column=Category -unique $categoryPathname/categ$filename  &
    } else {
        exec cp ${reqRoot}.categories $categoryPathname/categ$filename
    }

    if {[catch {exec sddsprocess -nowarn "-filter=column,Count,0,1.5,!" $categoryPathname/categ$filename -pipe=out | sdds2stream -pipe=in -col=IndirectName} waveformFiles]} {
        set waveformFiles ""
    }
    foreach waveformFile $waveformFiles {
        catch {exec chmod -w $pathname/${filename}.$waveformFile}
    }

    if {[file exists $pathname/beamlines]} {
        set beamlinePathname $pathname/beamlines
    } else {
        set beamlinePathname $pathname
    }
    if {![file exists ${reqRoot}.beamlines]  || \
          [file mtime ${reqRoot}.beamlines]<=[file mtime ${reqRoot}]} {
        if $SCRdebug {puts stderr "Launching beamline sort process"}
        exec sddssort $snapshot -column=Beamline -unique $beamlinePathname/beam$filename  &
    } else {
        exec cp ${reqRoot}.beamlines $beamlinePathname/beam$filename
    }
    if [catch {APSSCRCheckCASRSnapshot -snapshot $snapshot -logFile $logFile} badrows] {
        return -code error "APSSCRProcessSnapshot: $badrows"
    }
    if $badrows {
        #display log file
        if [file exist $logFile] {
            set tmpfile $logFile
        } else {
            set tmpfile /tmp/[APSTmpString]
            APSAddToTmpFileList -ID 1 -fileList $tmpfile
            if [catch {exec sddsprocess $snapshot -match=col,CAError=y -pipe=out \
                         sddsprintout -pipe=in "-title=Problem in reading following PVs..." \
                         -col=ControlName $tmpfile} result] {
                return -code error $result
            }
        }
    }
    if {[file exists $logFile] && $badrows} {
        if ![file size $logFile] {
            file delete $logFile
        } else {
            return -code ok [list $snapshot $logFile]
        }
    }
    return -code ok $snapshot
}

# Checks a sddscasr snapshot for bad data, returning the number of bad rows.
# Puts a line in the logFile with this info, also.
proc APSSCRCheckCASRSnapshot {args} {
    set snapshot ""
    set logFile ""
    APSStrictParseArguments {snapshot logFile}
    if ![file exists $snapshot] {
        return -code error "APSSCRCheckCASRSnapshot: not found: $snapshot"
    }
    set tmpfile /tmp/[APSTmpString]
    if [catch {exec sddsprocess $snapshot -match=col,CAError=y -pipe=out -nowarnings \
                 | sddsconvert -pipe=in $tmpfile -retain=col,ControlName -nowarning } result] {
        return -code error "APSSCRCheckCASRSnapshot: $result"
    }
    set rows [exec sdds2stream $tmpfile -rows=bar]
    if $rows {
        if ![file exist $logFile] {
            if [catch {exec sddsprintout $tmpfile $logFile \
                         -col=ControlName "-title=Missing $rows PVs..." } result] {
                return -code error "APSSCRCheckCASRSnapshot: $result"
            }
        }
        file delete -force $tmpfile
        if [catch {exec echo "\nAPSSCRCheckCASRSnapshot: $rows missing PVs\n" >> $logFile} result] {
            return -code error "APSSCRCheckCASRSnapshot: $result"
        }
    }

    if [catch {exec sddsprocess $snapshot -pipe=out \
                 -nowarning "-match=column,IsNumerical=y,ValueString=,&" \
                 | sdds2stream -pipe -rows,bare} result] {
        return -code error "APSSCRCheckCASRSnapshot: $result"
    } else {
        if {$result > 0} {
            APSSendEMail -address soliday@aps.anl.gov \
              -subject "Invalid SCR snapshot data" \
              -message "Look at $snapshot there may be an IOC that needs a reboot"
            APSSendEMail -address shang@aps.anl.gov \
              -subject "Invalid SCR snapshot data" \
              -message "Look at $snapshot there may be an IOC that needs a reboot"
            if [catch {exec echo "\nAPSSCRCheckCASRSnapshot: $rows missing PVs. Contact Bob Soliday or Hairong Shang to check for IOC problems.\n" > $logFile} result] {
                return -code error "APSSCRCheckCASRSnapshot: $result"
            }
            return -code error "APSSCRCheckCASRSnapshot: Invalid data in snapshot. There is probably an IOC with a hung task. Please contact Bob Soliday or Hairong Shang"
        }
    }

    return $rows
}

# Checks a snapshot for bad data, returning the number of bad rows.
# Puts a line in the logFile with this info, also.
proc APSSCRCheckSnapshot {args} {
    set snapshot ""
    set logFile ""
    set GPServer 0
    global apsUpgrade
    APSStrictParseArguments {snapshot logFile GPServer}
    if ![file exists $snapshot] {
        return -code error "APSSCRCheckSnapshot: not found: $snapshot"
    }
    if $GPServer {
        set matchOptions -match=column,ValueString=-,ValueString=,|
    } else {
        if {$apsUpgrade} {
            set matchOptions -match=column,ControlName=,CAError=y,|
        } else {
            set matchOptions -match=column,ControlName=,ControlType=,|,CAError=y,|
        }
    }
    if [catch {exec sddsprocess $snapshot -pipe=out \
                 $matchOptions -nowarning \
                 | sdds2stream -rows -pipe} result] {
        return -code error "APSSCRCheckSnapshot: $result"
    }
    set rows [lindex [split $result] 0]
    if $rows {
        if {[file exists $logFile]} {
            if [catch {exec echo "\nAPSSCRCheckSnapshot: $rows missing PVs\n" >> $logFile} result] {
                return -code error "APSSCRCheckSnapshot: $result"
            }
        } else {
            if [catch {exec echo "\nAPSSCRCheckSnapshot: $rows missing PVs\n" > $logFile} result] {
                return -code error "APSSCRCheckSnapshot: $result"
            }
        }
    }

    if [catch {exec sddsprocess $snapshot -pipe=out \
                 -nowarning "-match=column,IsNumerical=y,ValueString=,&" \
                 | sdds2stream -pipe -rows,bare} result] {
        return -code error "APSSCRCheckSnapshot: $result"
    } else {
        if {$result > 0} {
            APSSendEMail -address soliday@aps.anl.gov \
              -subject "Invalid SCR snapshot data" \
              -message "Look at $snapshot there may be an IOC that needs a reboot"
            APSSendEMail -address shang@aps.anl.gov \
              -subject "Invalid SCR snapshot data" \
              -message "Look at $snapshot there may be an IOC that needs a reboot"
            if [catch {exec echo "\nAPSSCRCheckSnapshot: $rows missing PVs. Contact Bob Soliday or Hairong Shang to check for IOC problems.\n" > $logFile} result] {
                return -code error "APSSCRCheckSnapshot: $result"
            }
            return -code error "APSSCRCheckSnapshot: Invalid data in snapshot. There is probably an IOC with a hung task. Please contact Bob Soliday or Hairong Shang"
        }
    }
    
    return $rows
}

proc APSCompareMachine {args} {
    global apsSCRSnapDir apsSCRRequestDir SCRdebug
    set snap1Label ""
    set machine ""
    set snapshot1 ""
    set snapshot2 ""
    set graphics 0
    set percentTolerance 0
    set absoluteTolerance 0
    set sortChoice none
    set statusCallback "APSNoOp"
    set showSimilarities 0
    set customComparisonList {}
    set pendIOTime 80
    #following arguments are for SRXrayBPM system custom compare
    set gapDiffTolerance 0.1
    set posDiffTolerance 0.1
    set minVoltage 0.01
    set waveformFile1 ""
    set waveformFile2 ""
    #minVoltage is the minimum acceptable blade voltage
    set partial 0
    if $SCRdebug { puts stderr "in APSCompareMachine" }
    if {[APSStrictParseArguments {snap1Label machine snapshot1 snapshot2 graphics statusCallback partial \
                                    percentTolerance absoluteTolerance sortChoice \
                                    showSimilarities customComparisonList \
                                    gapDiffTolerance posDiffTolerance minVoltage pendIOTime \
                                    waveformFile1 waveformFile2 \
                                } ]==-1} {
        return -code error "Bad arguments to APSCompareMachine"
    }
    if $SCRdebug { puts stderr "Parsing done."}
    
    if $absoluteTolerance>0 {
        set percentTolerance 0
    }
    if ![string length $snapshot2] {
        if ![file exists [file join $apsSCRRequestDir $machine.req]] {
            return -code error "APSCompareMachine: Bad machine name $machine"
        }
    } 
    set tmproot /tmp/[APSTmpString]
    APSAddToTmpFileList -ID APSCompareMachine -fileList $tmproot
    APSAddToTmpFileList -ID APSCompareMachineError -fileList $tmproot
    
    # snapshot1 needs $apsSCRSnapDir/$machine path 
#    set snapshot1 $apsSCRSnapDir/$machine/$snapshot1
    set waveform1 [file root $snapshot1].waveform
    if ![file exist $waveform1] {
        set waveform1 $waveform1.gz
    }
    if ![file exists $snapshot1] {
        set snapshot1 ${snapshot1}.gz
    }
    if ![file exists $snapshot1] {
        return -code error "APSCompareMachine: snapshot1 file $snapshot1 does not exist"
    }
    if $SCRdebug { puts stderr "Snapshot1 found."}
 
    if [string length $snapshot2]==0 {
        if $SCRdebug { puts stderr "Making snapshot2"}
        if [string length $statusCallback] {
            eval $statusCallback {"Making snapshot..."}
        }
        set snapshot2 ${tmproot}.snap2 
        if [catch {APSSaveMachine -machine $machine -temporary 1 -pendIOTime $pendIOTime \
		       -snapshot $snapshot2 \
		       -description "APSCompareMachine temporary snapshot" } result] {
            APSDeleteTmpFileList -ID APSCompareMachine
            return -code error "APSCompareMachine: Unable to make temporary snapshot: $result"
        }
        set snapshot2 [lindex $result 0]
	#keep only pvs that are in snapshot1
	if [catch {exec sddsselect $snapshot2 $snapshot1 $snapshot2.1 -match=ControlName } result] {
	    APSDeleteTmpFileList -ID APSCompareMachine
            return -code error "APSCompareMachine: Unable to select matching pvs: $result"
	}
	exec mv $snapshot2.1 $snapshot2
        if [string length $statusCallback] {
            eval $statusCallback {"Snapshot done."}
        }
        set DifferenceLabel Prsnt-Rfrnc
        set snap2Label "present configuration"
        set Value2Label "Present"
        set Value1Label "Reference"
        if $SCRdebug { puts stderr "snapshot2 is $snapshot2" }
        set snap2IsTmp 1
    } else {
        set DifferenceLabel Dtst2-Dtst1
        set snap2Label $snapshot2
        set Value1Label Dataset1
        set Value2Label Dataset2
        set snap2IsTmp 0
    }
    
    if ![file exists $snapshot2] {
        set snapshot2 ${snapshot2}.gz
    }
    if [string length $waveformFile1] {
        set waveform1 $waveformFile1
    }
    if [string length $waveformFile2] {
        set waveform2 $waveformFile2
    } else {
        set waveform2 [file root $snapshot2].waveform
        if ![file exist $waveform2] {
            set waveform2 $waveform2.gz
        }
    }
    if ![file exists $snapshot2] {
        APSDeleteTmpFileList -ID APSCompareMachine
        return -code error "APSCompareMachine: snapshot2 $snapshot2 does not exist"
    }
    if ![string length $snap1Label] {
        set snap1Label "$snapshot1"
    }
    if !$partial {
        if [catch {APSCompareNonMatchingPVs -snapshot1 $snapshot1 -snapshot2 $snapshot2 -snap1Label $snap1Label -snap2Label $snap2Label} result] {
            return -code error "APSCompareMachine: error in checking non-matching pvs: $result"
        }
    }
    if {[file exist $waveform1] && [file exist $waveform2]} {
        set tmpRoot /tmp/[APSTmpString]
        set waveforms [exec sdds2stream -par=WaveformPV $waveform1]
        set fileList ""
        set index 0
        foreach pv $waveforms {
            eval $statusCallback {"Comparing waveform ${pv} ..."}
            incr index
            if [catch {exec sddsprocess /home/helios/oagData/SCR/requestFiles/${machine}.req \
                         -match=col,ControlName=$pv -pipe=out \
                         | sdds2stream -pipe -col=Tolerance } tol] {
                return -code error "APSCompareMachine: unable to get the tolerance of $pv: $result"
            }
            if [catch {exec sddsprocess $waveform1 $tmpRoot.1a -match=par,WaveformPV=$pv
                exec sddsprocess $waveform2 $tmpRoot.2a -match=par,WaveformPV=$pv } result] {
                exec echo $pv does not exist in one of $waveform > $tmpRoot.index
            } else {
                if {[catch {exec sddsprocess $tmpRoot.1a -nowarn "-scan=column,NewWaveform,Waveform,%lf,type=double"
                    exec sddsprocess $tmpRoot.2a -nowarn "-scan=column,NewWaveform,Waveform,%lf,type=double"} result]} {
                    set pre ""
                } else {
                    set pre "New"
                }
                if [catch {exec sddsconvert $tmpRoot.1a -rename=col,${pre}Waveform=Reference -pipe=out \
                             | sddsxref -pipe $tmpRoot.2a -rename=col,${pre}Waveform=Value \
                             | sddsprocess -pipe=in  $tmpRoot.diff$index -nowarn \
                             "-define=column,Difference,Value Reference -" \
                             "-define=col,AbsDiff,Difference abs" \
                             "-test=col,AbsDiff $tol >"  -nowarnings} result] {
                    return -code error "APSCompareMachine (waveform2) :$result"
                }
                set rows [lindex [exec sdds2stream -rows=bar $tmpRoot.diff$index] 0]
                if $rows {
                    exec sddsprintout $tmpRoot.diff$index $tmpRoot.$index \
                      "-title=Waveform $pv has $rows rows different (more than $tol) between $waveform1 and $waveform2" \
                      -col=Index -col=Value,label=$Value1Label -col=Reference,label=$Value2Label -col=Difference
                } else {
                    exec echo no difference of $pv between $waveform1 and $waveform2 \n\n > $tmpRoot.$index
                }
            }
            lappend fileList $tmpRoot.$index
        }
        if [llength $fileList]>1 {
            eval exec cat $fileList > $tmpRoot.print
            set printFile $tmpRoot.print
        } else {
            set printFile $fileList
        }
      
        APSFileDisplayWindow [APSUniqueName .] \
          -fileName $printFile -deleteOnClose 1 -width 120 -height 50 
    }
    
    if {[llength $customComparisonList]==0 \
          || ([llength $customComparisonList]==1 && \
                [string length [lindex $customComparisonList 0]]==0)} {
        if $SCRdebug {
            puts stderr "Doing normal comparison."
        }
        set printFile1 ${tmproot}-1
        set sdiffFile ${tmproot}-2
        set printFile2 ${tmproot}-3
        set diffFile ${tmproot}-4
        set bothSnaps ${tmproot}-5
        set enumDiff ${tmproot}-6

        APSAddToTmpFileList -ID APSCompareMachine -fileList "$bothSnaps $diffFile"
        APSAddToTmpFileList -ID APSCompareMachineError -fileList \
          "$sdiffFile $enumDiff $printFile1 $printFile2 $bothSnaps $diffFile"
        
        if $absoluteTolerance>0 {
            set redefToleranceArg "-redefine=column,Tolerance,$absoluteTolerance" 
            set toleranceLabel "\n  using absolute tolerance of $absoluteTolerance\n"
        } elseif $percentTolerance>0 {
            set redefToleranceArg "-redefine=column,Tolerance,Reference [expr $percentTolerance/100.0] * abs"
            set toleranceLabel "\n  using percent tolerance of $percentTolerance\n"
        } else {
            set redefToleranceArg "-filter=col,Tolerance,0,1e300"
            set toleranceLabel "\n  using default tolerances\n"
        }

        if [string length $statusCallback] {
            eval $statusCallback {"Doing comparison..."}
        }

        if {[catch {APSSCRLogAction -file [file tail $snapshot1] \
                      -action compare} result] && \
              [string length $statusCallback]} {
            eval $statusCallback {"$result"}
        }

        if {!$snap2IsTmp && \
              [catch {APSSCRLogAction -file [file tail $snapshot2] \
                        -action compare} result] && \
              [string length $statusCallback]} {
            eval $statusCallback {"$result"}
        }

        if $showSimilarities {
            set extraQualifier ""
            set compareType similarities
        } else {
            set extraQualifier ",!"
            set compareType differences
        }
        
        if [catch {exec sddsconvert $snapshot2 -pipe=out \
                     -rename=col,ValueString=NewValueString -binary -nowarn \
                     | sddsxref $snapshot1 -pipe -match=ControlName -take=* -nowarn -reuse=row \
                     | tee $bothSnaps \
                     | sddsprocess -pipe -nowarnings -match=col,IsNumerical=+y -nowarn "-match=col,ValueString=WaveformPV,!"  \
                     -numberTest=column,ValueString,strict \
                     -numberTest=column,NewValueString,strict \
                     "-scan=col,Value,NewValueString,%lf" \
                     "-scan=col,Reference,ValueString,%lf" \
                     $redefToleranceArg \
                     "-define=column,Difference,Value Reference -" \
                     | tee $diffFile \
                     | sddsprocess -pipe=in $sdiffFile -nowarn \
                     "-define=col,AbsDifference,Difference abs,format_string=%12.6f" \
                     "-define=col,ExcessDifference,AbsDifference Tolerance -" \
                     "-define=col,FracAbsDifference,Value abs Reference abs + 2 / sqrt 0 == pop ? -1 : AbsDifference swap / \$ " \
                     "-filter=col,ExcessDifference,-1e300,0$extraQualifier" } result] {
            return -code error "APSCompareMachine7: $result"
        }
            
        if [catch {exec sddsprocess $bothSnaps $enumDiff "-match=col,IsNumerical=+y,!" \
                     "-match=col,ValueString=@NewValueString$extraQualifier" -nowarning } result] {
            return -code error "APSCompareMachine8: $result"
        }

        catch {exec sdds2stream $sdiffFile -rows} rows
        exec echo $rows with significant $compareType > $printFile1
        switch -- $sortChoice {
            byValue {
                set sortArg -column=Reference,incr
            }
            byError {
                set sortArg -column=AbsDifference,decr
            }
            byName {
                set sortArg -column=ControlName
            }
            byFracError {
                set sortArg -column=FracAbsDifference,decr
            }
            default {
                set sortArg ""
            }
        }
        if [string length $sortArg] {
            set newDiff ${tmproot}-7
            if ![catch {exec sddssort $sdiffFile $newDiff $sortArg} result] {
                file rename -force $newDiff $sdiffFile
            }
        }

        if [string length $statusCallback] {
            eval $statusCallback {"Making printout..."}
        }


        exec sddsbreak $sdiffFile -pipe=out -rowlimit=42 \
          | sddsprintout -column=ControlName,format=%-30s -pipe \
          -nowarning -postpagelines=4 \
          "-title=Numerical $compareType between $snap1Label and $snap2Label $toleranceLabel"  \
          "-column=Reference,label=$Value1Label,format=%12.5g" \
          "-column=Value,label=$Value2Label,format=%12.5g" \
          "-column=Difference,label=$DifferenceLabel,format=%12.5g" \
          "-column=Tolerance,label=Toler.,format=%10.3g"  \
          "-col=Description,format=%-12s" \
          >> $printFile1

        catch {exec sdds2stream $enumDiff -rows} rows
        exec echo $rows with enumerated-value $compareType > $printFile2
        if [string compare $sortChoice byName]==0 {
            set newDiff ${tmproot}-8
            if ![catch {exec sddssort $enumDiff $newDiff $sortArg} result] {
                file rename -force $newDiff $enumDiff
            }
        }
        exec sddsbreak $enumDiff -rowlimit=42 -pipe=out \
          | sddsprintout -pipe -column=ControlName,format=%-30s \
          -postpagelines=4 -nowarning \
          "-title=Enumerated $compareType between $snap1Label and $snap2Label\n\n"  \
          "-column=ValueString,label=$Value1Label,format=%20s" \
          "-column=NewValueString,label=$Value2Label,format=%20s" \
          "-col=Description,format=%-12s" \
          >> $printFile2 

        if $graphics {
	    exec echo "\n\n" >> $tmproot.space
	    exec cat $printFile2 $tmproot.space $printFile1 > $tmproot
	    file delete -force $tmproot.space
            # display is done by this procedure
            APSFileDisplayWindow [APSUniqueName .] \
              -fileName $tmproot -deleteOnClose 1 -width 120 -height 50 \
              -comment "Numerical-value snapshot $compareType" \
              -printCommand "enscript -r"
            #APSFileDisplayWindow [APSUniqueName .] \
            #  -fileName $printFile2 -deleteOnClose 1 -width 120 -height 50 \
             # -comment "Enumerated-value snapshot $compareType" \
              #-printCommand "enscript -r"
            set doNotDelete 0
            switch $machine {
                PLET {
                    exec plotPLETcomparison $diffFile $enumDiff $tmproot \
                      $snap1Label $snap2Label 1 & 
                }
                default {
                    eval file delete $diffFile $enumDiff
                }
            }
            # file delete $sdiffFile $bothSnaps
            #after 120000 "APSDeleteTmpFileList -ID APSCompareMachineError"
           # return {}
        } else {
            file delete $bothSnaps $diffFile
            APSDeleteTmpFileList -ID APSCompareMachine
            return [list $sdiffFile $enumDiff $printFile1 $printFile2]
        }

    } else {
        # custom comparisons using caller-supplied procedures.
        if $SCRdebug {
            puts stderr "Doing [llength customComparisonList] custom comparison(s)."
            puts stderr "snapshot1: $snapshot1"
            puts stderr "snapshot2: $snapshot2"
            puts stderr "machine: $machine"
        }
        set results ""
        foreach customProc $customComparisonList {
            if {$machine=="SRXrayBPM"} {
                if [catch {eval $customProc -machine $machine -snapshot1 $snapshot1 \
                               -gapDiffTolerance $gapDiffTolerance \
                               -snapshot2 $snapshot2 -snap1Label \"$snap1Label\" \
                               -Value1Label \"$Value1Label\" -Value2Label \"$Value2Label\" \
                               -snap2Label \"$snap2Label\" -DifferenceLabel \"$DifferenceLabel\" \
                               -posDiffTolerance $posDiffTolerance -minVoltage $minVoltage } result] {
                    APSDeleteTmpFileList -ID APSCompareMachineError
                    return -code error "Problem with $customProc: $result"
                }     
            } else {
                if [catch {eval $customProc -machine $machine \
                             -snapshot1 $snapshot1 -snapshot2 $snapshot2} result] {
                    APSDeleteTmpFileList -ID APSCompareMachineError
                    return -code error "Problem with $customProc: $result"
                }
            }
            lappend results $result
        }
        return $results
    }
}

proc APSCompareNonMatchingPVs {args} {
    set snapshot1 ""
    set snapshot2 ""
    set snap1Label ""
    set snap2Label ""
    APSStrictParseArguments {snapshot1 snapshot2 snap1Label snap2Label}
    set tmproot /tmp/[APSTmpString]
    
    #compare matching pvs, and display non-match pvs
    
    if [catch {exec sddsselect $snapshot1 $snapshot2 $tmproot.snap1n -match=ControlName -invert -nowarnings
	exec sddsselect $snapshot2 $snapshot1 $tmproot.snap2n -match=ControlName -invert -nowarnings } result] {
	return -code error "APSCompareMachine6a: $result"
    }
    set rows1 [exec sdds2stream -rows=bar $tmproot.snap1n]
    set rows2 [exec sdds2stream -rows=bar $tmproot.snap2n]
    if $rows1 {
	exec sddsbreak $tmproot.snap1n -pipe=out -rowlimit=42 \
	    | sddsprintout -column=ControlName,format=%-30s -pipe \
	    -nowarning -postpagelines=4 \
	    "-title=Non-matching pvs which exist in $snap1Label but not in $snap2Label"  \
	    "-column=ValueString,label=ValueInRef1,format=%-30s" \
	    "-col=Description,format=%-12s" \
	    >> $tmproot.printout.1n
	APSAddToTmpFileList -ID APSCompareMachine -fileList $tmproot.printout.1n
    }
    if $rows2 {
	exec sddsbreak $tmproot.snap2n -pipe=out -rowlimit=42 \
	    | sddsprintout -column=ControlName,format=%-30s -pipe \
	    -nowarning -postpagelines=4 \
	    "-title=Non-matching pvs which exist in $snap2Label but not in $snap1Label"  \
	    "-column=ValueString,label=ValueInRef2,format=%-30s" \
	    "-col=Description,format=%-12s" \
	    >> $tmproot.printout.2n
	APSAddToTmpFileList -ID APSCompareMachine -fileList $tmproot.printout.2n
	if $rows1 {
	    exec echo "\n\n" >> $tmproot.space1
	    exec cat  $tmproot.printout.1n $tmproot.space1 $tmproot.printout.2n > $tmproot.printNonmatch
	} else {
	    exec cp $tmproot.printout.2n  $tmproot.printNonmatch
	}
    } elseif $rows1 {
	exec cp $tmproot.printout.1n $tmproot.printNonmatch
    }
    if {$rows1 || $rows2} {
	APSFileDisplayWindow [APSUniqueName .] \
	    -fileName $tmproot.printNonmatch -deleteOnClose 1 -width 120 -height 50 \
	    -comment "Non-matching PVs in two snapshot" \
	    -printCommand "enscript -r"
	
    }
}
proc APSListSCRMatch {args} {
    global apsSCRRequestDir apsSCRSnapDir apsSCRAtticDir
    set machine ""
    set startYear 0
    set startDay 0
    set startMonth 0
    set endYear 0
    set endDay 0
    set endMonth 0
    set descriptionFragment "" 
    set statusCallback puts
    set parent ""
    set routine 0
    set shift 0
    set callback ""
    set height 10
    set listOrder increasing
    set selectMode single
    set attic 0
    set label ""
    set labelFont ""
    if [APSStrictParseArguments \
          {machine startYear startDay startMonth endYear endDay endMonth \
             descriptionFragment statusCallback height \
             parent routine shift callback listOrder selectMode attic label labelFont}]==-1 {
        return -code error "APSListSCRMatch: bad arguments"
    }
    if $routine {
        if {$shift} {
            set descriptionFragment "Routine save (shift change)"
        } else {
            set descriptionFragment "Routine save"
        }
        #set startYear 1990
        #set endYear 2090
        #set startDay 1
        #set endDay 1
        #set startMonth 1
        #set endMonth 1
    }
    
    if {[string length $descriptionFragment] == 0} {
        $statusCallback "Supply a description search string (use * for everything)"
        return
    }
    if {[winfo exists ${parent}.apsSCRlist]} {destroy ${parent}.apsSCRlist}
    if {[winfo exists ${parent}.apsSCRcateg]} {destroy ${parent}.apsSCRcateg}
    $statusCallback "Searching for $machine..." 
    set oldDir [pwd]
    set snapDir $apsSCRSnapDir/$machine
    if $attic {
        set snapDir $apsSCRAtticDir/$machine
    } 
    cd $snapDir
    if {[file exists collapsed]} {
        set collapsedDirectoryFound 1
        cd collapsed
    } else {
        set collapsedDirectoryFound 0
    }
    set fileList \
      [APSFindFilesBetweenDates -directory . -rootname c$machine -tailsOnly 1 \
         -startDateList \
         [APSFormatDate -year $startYear -month $startMonth -day $startDay \
            -twoDigitYear 0 -dateFormat list] \
         -endDateList \
         [APSFormatDate -year $endYear -month $endMonth -day $endDay \
            -twoDigitYear 0 -dateFormat list] \
         -extensionList {""}
       ]
    if [llength $fileList]==0 {
        $statusCallback "No match found for dates given."
        cd $oldDir
        return
    }
    set fileList [lsort -$listOrder $fileList]
    set listData /tmp/[APSTmpString]
    set tmpID [APSUniqueName tmpID]
    APSAddToTmpFileList -ID $tmpID -fileList $listData
    regsub -all {\[} ${descriptionFragment} {\[} descriptionFragment
    if [catch {eval exec sddscombine $fileList -pipe=out -merge \
                | sddsprocess -pipe=in $listData \
                 \"-match=col,SnapshotDescription=+*${descriptionFragment}*\" -nowarnings} result] {
        $statusCallback "Error scanning descriptions of snapshots!"
        APSAlertBox [APSUniqueName .] -errorMessage "$result" 
        APSDeleteTmpFileList -ID $tmpID
        cd $oldDir
        return
    }
    if {$collapsedDirectoryFound} {
        cd ..
    }
    if [catch {exec sdds2stream -rows $listData} result] {
        $statusCallback "Error reading list of matching snapshots"
        cd $oldDir
        APSDeleteTmpFileList -ID $tmpID
        return
    }
    set count [lindex [split $result] 0]
    if {$count == 0} {
        $statusCallback "No match to search criteria"
        cd $oldDir
        APSDeleteTmpFileList -ID $tmpID
        return
    }

    global apsSCRMakeLBoxTextLineMaxWidth
    set apsSCRMakeLBoxTextLineMaxWidth 0
    APSSDDSListbox .apsSCRlist -parent ${parent} -height $height -selectMode $selectMode \
      -widthLimit 120 -trim 1 -label $label -labelFont $labelFont \
      -fileName $listData -labelMaker APSSCRMakeLBoxTextLine -arrayName descriptionArray \
      -page 0 -callback $callback -columnList {SnapshotFilename TimeStamp SnapshotDescription} \
      -contextHelp "Double-click to select a snapshot" -doneButton 0

    $statusCallback "Pick a configuration to use"
    cd $oldDir
    APSDeleteTmpFileList -ID $tmpID
}

proc APSListSCRMatchSetArray {args} {
    global apsSCRRequestDir apsSCRSnapDir apsSCRSystem
    set machine $apsSCRSystem
    set startYear 0
    set startDay 0
    set startMonth 0
    set endYear 0
    set endDay 0
    set endMonth 0
    set descriptionFragment "" 
    set statusCallback puts
    set parent ""
    set routine 0
    set shift 0
    set callback ""
    set arrayName ""
    set PreferredOnly 0
    set height 10
    set listOrder increasing
    if [APSStrictParseArguments \
          {machine startYear startDay startMonth endYear endDay endMonth \
             descriptionFragment statusCallback height \
             parent routine shift callback arrayName PreferredOnly listOrder}]==-1 {
        return -code error "APSListSCRMatch: bad arguments"
    }
    set old_apsSCRSystem $apsSCRSystem
    set apsSCRSystem $machine
    set apsSCRSystem $old_apsSCRSystem

    if $routine {
        if {$shift} {
            set descriptionFragment "Routine save (shift change)"
        } else {
            set descriptionFragment "Routine save"
        }
        #set startYear 1990
        #set endYear 2090
        #set startDay 1
        #set endDay 1
        #set startMonth 1
        #set endMonth 1
    }
    
    if {[string length $descriptionFragment] == 0} {
        $statusCallback "Supply a description search string (use * for everything)"
        set apsSCRSystem $old_apsSCRSystem
        return
    }
    if {[winfo exists ${parent}.snaplist]} {destroy ${parent}.snaplist}
    if {[winfo exists ${parent}.apsSCRlist]} {destroy ${parent}.apsSCRlist}
    if {[winfo exists ${parent}.apsSCRcateg]} {destroy ${parent}.apsSCRcateg}
    $statusCallback "Searching for $machine..." 
    set oldDir [pwd]
    set snapDir $apsSCRSnapDir/$machine
    cd $snapDir
    if {[file exists collapsed]} {
        set collapsedDirectoryFound 1
        cd collapsed
    } else {
        set collapsedDirectoryFound 0
    }
    set fileList \
      [APSFindFilesBetweenDates -directory . -rootname c$machine -tailsOnly 1 \
         -startDateList \
         [APSFormatDate -year $startYear -month $startMonth -day $startDay \
            -twoDigitYear 0 -dateFormat list] \
         -endDateList \
         [APSFormatDate -year $endYear -month $endMonth -day $endDay \
            -twoDigitYear 0 -dateFormat list] \
         -extensionList {""}
       ]
    if {[llength $fileList] != 0} {
        set fileList [lsort -$listOrder $fileList]
        set listData /tmp/[APSTmpString]
        set tmpID [APSUniqueName tmpID]
        APSAddToTmpFileList -ID $tmpID -fileList $listData
        regsub -all {\[} ${descriptionFragment} {\[} descriptionFragment
        if [catch {eval exec sddscombine $fileList -pipe=out -merge \
                       | sddsprocess -pipe=in $listData \
                       \"-match=col,SnapshotDescription=+*${descriptionFragment}*\" -nowarnings} result] {
            $statusCallback "Error scanning descriptions of snapshots!"
            APSAlertBox [APSUniqueName .] -errorMessage "$result" 
            APSDeleteTmpFileList -ID $tmpID
            cd $oldDir
            set apsSCRSystem $old_apsSCRSystem
            return
        }
        if [catch {exec sdds2stream -rows $listData} result] {
            $statusCallback "Error reading list of matching snapshots"
            APSDeleteTmpFileList -ID $tmpID
            cd $oldDir
            set apsSCRSystem $old_apsSCRSystem
            return
        }
        set count [lindex [split $result] 0]
        if {$count == 0} {
            $statusCallback "No match to search criteria"
            APSDeleteTmpFileList -ID $tmpID
            cd $oldDir
            set apsSCRSystem $old_apsSCRSystem
            return
        }
    }
    if {$collapsedDirectoryFound} {
        cd ..
    }

    APSFrame .apsSCRlist -parent $parent -label "" -relief flat
    set w $parent.apsSCRlist.frame
    
    global apsSCRMakeLBoxTextLineMaxWidth apsSCRTopDirectory
    set apsSCRMakeLBoxTextLineMaxWidth 0
    global $arrayName
    set ${arrayName}(Filename) ""
    set ${arrayName}(ShortFilename) ""
    set ${arrayName}(Description) ""
    set ${arrayName}(IsPreferredFile) 0
    
    if {!$PreferredOnly} {
        if {[llength $fileList] != 0} {
            APSSDDSListbox .snaplist -parent $w -height $height \
                -widthLimit 120 -trim 1 \
                -fileName $listData -labelMaker APSSCRMakeLBoxTextLine \
                -arrayName apsSCRMatchListArray$arrayName$machine \
                -page 0 -callback "APSListSCRMatchSetArrayCallback $machine apsSCRMatchListArray$arrayName$machine $callback 0 $arrayName " \
                -columnList {SnapshotFilename TimeStamp SnapshotDescription} \
                -contextHelp "Double-click to select a snapshot" -doneButton 0 \
                -label "Snapshot Choices"
        }
    }
    if {!$routine && [file exists [file join $apsSCRTopDirectory preferredChoices $machine.sdds]]} {
        global apsSCRTopDirectory
        APSSDDSListbox .preflist -parent $w -height 4 \
          -widthLimit 120 -trim 1 -label "Preferred/Reference Choices" \
          -fileName $apsSCRTopDirectory/preferredChoices/$machine.sdds \
          -labelMaker APSSCRMakePrefLBoxTextLine \
          -arrayName apsSCRMatchListPreferredArray$arrayName$machine \
          -page 0 -callback "APSListSCRMatchSetArrayCallback $machine apsSCRMatchListPreferredArray$arrayName$machine $callback 1 $arrayName" \
          -columnList {ChoiceName Suffix} \
          -contextHelp "Double-click to select a preferred file." -doneButton 0
    }

    $statusCallback "Pick a configuration to use"
    if {[llength $fileList] != 0} {
        APSDeleteTmpFileList -ID $tmpID
    }
    cd $oldDir
    set apsSCRSystem $old_apsSCRSystem
}

proc APSListSCRMatchSetArrayCallback {machine sourceArray callback mode arrayName index} {
    global $arrayName $sourceArray
    global apsSCRSnapDir 
    set ${arrayName}(FileNotFound) 1
    if $mode {
        # preferred file 
        set item [lindex [set $sourceArray\(ChoiceName\)] $index]
        if {![catch {APSSCRResolvePreferredFileChoice -system $machine -choice $item} file] && \
		[file exists [file join $apsSCRSnapDir $machine $file]]} {
            set file [file readlink [file join $apsSCRSnapDir $machine $file]]
	    if [catch {file readlink [file join $apsSCRSnapDir $machine [file tail $file]]} file1] {
		#do nothing, 
	    } else {
		set file $file1
	    }
            set filename0 [file tail $file]
            set ${arrayName}(IsPreferredFile) $mode
            set ${arrayName}(Filename) [file rootname $filename0]
            if {[file exists $apsSCRSnapDir/$machine/collapsed]} {
                set ${arrayName}(Description) \
                  [join [APSGetSDDSColumn -page 0 -column SnapshotDescription \
                           -fileName $apsSCRSnapDir/$machine/collapsed/c[file rootname $filename0]]]
            } else {
                set ${arrayName}(Description) \
                  [join [APSGetSDDSColumn -page 0 -column SnapshotDescription \
                           -fileName $apsSCRSnapDir/$machine/c[file rootname $filename0]]]
            }
            set ${arrayName}(FileNotFound) 0
            set ${arrayName}(Lattice) $item
        }
    } else {
        # normal file
        set ${arrayName}(IsPreferredFile) $mode
        set ${arrayName}(Filename) [lindex [set $sourceArray\(SnapshotFilename\)]  $index]
        set ${arrayName}(Description) [lindex [set $sourceArray\(SnapshotDescription\)] $index]
        set ${arrayName}(FileNotFound) 0
    }
    eval $callback ${arrayName} $machine
}

proc APSSCRMakePrefLBoxTextLine {args} {
    global apsSCRMakeLBoxTextLineMaxWidth
    set dataList {}
    set widthList {}
    APSStrictParseArguments {dataList widthList}
    set choice [lindex $dataList 0]
    set wc [lindex $widthList 0]
    set sform [format "%%%lds" $apsSCRMakeLBoxTextLineMaxWidth]
    return [format $sform $choice]
}

proc APSSCRMakeLBoxTextLine {args} {
    global apsSCRMakeLBoxTextLineMaxWidth
    set dataList {}
    set widthList {}
    APSStrictParseArguments {dataList widthList}
    set filename [lindex $dataList 0]
    set wf [lindex $widthList 0]
    set timestamp [lindex $dataList 1]
    set wt [lindex $widthList 1]
    set description [lindex $dataList 2]
    set wd [lindex $widthList 2]
    set rootname [file rootname $filename]
    set sform [format "%%%lds    %%%lds" $wt $wd] 
    set result [format $sform $timestamp $description]
    if [string length $result]>$apsSCRMakeLBoxTextLineMaxWidth {
        set apsSCRMakeLBoxTextLineMaxWidth [string length $result]
    }
    return $result
}

proc APSSCRFilterFileCallback {index} {
    global apsSCRFilterFileName apsSCRFilterFileDescription apsSCRFilterFileArray 
    global apsSCRFilterCategory apsSCRFilterBeamline apsSCRInvertFilterFile 
    global apsSCRPVNameFilter apsSCRPVSetpointFilter
    set apsSCRFilterFileName [lindex $apsSCRFilterFileArray(Filename) $index]
    set apsSCRFilterFileDescription [lindex $apsSCRFilterFileArray(Description) $index]
    set apsSCRPVNameFilter [lindex $apsSCRFilterFileArray(Wildcard) $index]
    set apsSCRInvertFilterFile 0
    set apsSCRPVSetpointFilter 0
    foreach elem [array names apsSCRFilterCategory] {
        set apsSCRFilterCategory($elem) 0
        foreach category [split [lindex $apsSCRFilterFileArray(CategoryList) $index] ", "] {
            if [string match $category $elem] {
                set apsSCRFilterCategory($elem) 1
            }
        }
    }
    foreach elem [array names apsSCRFilterBeamline] {
        set apsSCRFilterBeamline($elem) 0
        foreach beamline [split [lindex $apsSCRFilterFileArray(BeamlineList) $index] ", "] {
            if [string match $beamline $elem] {
                set apsSCRFilterBeamline($elem) 1
            }
        }
    }
}

proc APSSCRMakeLBoxTextFilterFile {args} {
    set dataList ""
    set widthList ""
    APSStrictParseArguments {dataList widthList}
    set description [lindex $dataList 1]
    set wd [lindex $widthList 1]
    if $wd<60 {set wd 60}
    set sform [format "%%%lds" $wd]
    return [format $sform $description]
}

proc APSSCRPVFilterWidget {widget args} {
    global apsSCRPVNameFilter apsSCRFilterFileDir apsSCRFilterFileName 
    global apsSCRFilterFileDescription apsSCRPVSetpointFilter
    global apsSCRInvertFilterFile apsSCRSuppressSingleBeamline

    set parent ""
    set fileName ""
    set rootname ""
    set snapDir ""
    APSStrictParseArguments {parent fileName rootname snapDir}

    set name [APSRemoveGzipExtension $fileName]

    APSFrame $widget -parent $parent -packOption "-side top -expand true -fill both"
    set w ${parent}${widget}.frame

    set categories $snapDir/categories/categ$name
    if ![file exists $categories] {
        set categories $snapDir/categ$name
    }
    if ![file exists $categories] {
        if catch {exec sddssort $snapDir/$fileName $categories -unique -column=Category} result] {
            return -code error "Error in generating category file (most probably you do not have write permission to $snapDir): $result"
        }
    }
    APSSDDSCheckButtons .list1 -parent $w -fileName $categories -column Category \
      -arrayName apsSCRFilterCategory -page 0 -limit 7 -duplicate 0 -modal 0 \
      -noOk 1 -noCancel 1 -buttonSize small

    set beamlines $snapDir/beamlines/beam$name
    if ![file exists $beamlines] {
        set beamlines $snapDir/beam$name
    }
    if ![file exists $beamlines] {
        if [catch {exec sddssort $snapDir/$fileName $beamlines -unique -column=Beamline} result] {
            return -code error "Error in generating beamline file (most probably you do not have write permission to $snapDir): $result"
        }
    }
    set showBeamlines 1
    if $apsSCRSuppressSingleBeamline {
        set nbeamlines [exec sdds2stream -rows=bare $beamlines]
        if $nbeamlines<=1 {
            set showBeamlines 0
        }
    }
    if $showBeamlines {
        APSSDDSCheckButtons .list2 -parent $w -fileName $beamlines -column Beamline \
          -arrayName apsSCRFilterBeamline -page 0 -limit 7 -duplicate 0 -modal 0 \
          -noOk 1 -noCancel 1 -buttonSize small
    } else {
        global apsSCRFilterBeamline
        set apsSCRFilterBeamline(DUMMY) 1
    }

    APSCheckButtonFrame .setpoints \
      -parent $w \
      -label "" \
      -buttonList {"Setpoints Only"} \
      -variableList "apsSCRPVSetpointFilter"

    if [winfo exists $w.pvname] {
        destroy $w.pvname
    }
    APSLabeledEntry .pvname -parent $w -textVariable apsSCRPVNameFilter -width 40  -label "PV name filter: " \
      -packOption "-side top" \
      -contextHelp "Enter a string to use in filtering process variable names prior to review/restore.\
 The character * matches zero or more arbitrary characters, while the character ? matches any single\
 character.  So S*A:Q1* would match all the A:Q1 quadrupole process variables for the SR.\n\n\
 Square brackets (\[ and \]) can be used to indicate sets of values.  For example, S\[1-4\]A:Q1*\
 would match all the A:Q1 quadrupole PVs in sectors 1 through 4, while S\[1-3\]9\[AB\]:Q1* would match*\
 A:Q1 and B:Q1 PVs in sectors 19, 29, and 39."

    bind $w.pvname.entry <Key> "set apsSCRInvertFilterFile 0"

    set filterFileList $apsSCRFilterFileDir/${rootname}-FilterFiles.sdds
    if [file exists $filterFileList] {
        # add a scroll widget with filter file selections
        APSFrame .ffframe -parent $w -label "Predefined filters" -packOption "-side top -expand true -fill both"
        APSSDDSListbox .fflist -parent $w.ffframe.frame -fileName $filterFileList -height 8 \
          -labelMaker APSSCRMakeLBoxTextFilterFile -arrayName apsSCRFilterFileArray \
          -page 0 -callback APSSCRFilterFileCallback \
          -columnList {Filename Description Wildcard BeamlineList CategoryList} \
          -contextHelp "Double-click to select a predefined filter." \
          -doneButton 0 -buttonSize small \
          -packOption "-side top -expand true -fill y"
        set apsSCRFilterFileDescription ""
        set apsSCRFilterFileName ""
        set apsSCRInvertFilterFile 0
        APSLabeledOutput .ffdescript -parent $w.ffframe.frame -label "Choice: "  \
          -textVariable apsSCRFilterFileDescription -width 40 \
          -contextHelp "Displays the choice of predefined filter.  If blank, no predefined filter is used."
        APSCheckButtonFrame .invert -parent $w.ffframe.frame -label "" \
          -buttonList {"Invert filter"} -variableList apsSCRInvertFilterFile \
          -orientation horizontal -relief flat -contextHelp \
          "Inverts the selection of PVs with the filter file, so that the items selected are all those that would  normally be rejected.  For some predefined filters (those using wildcards instead of a file), this feature is disabled." \
          -commandList {APSSCRInvertFilterFileCallback}

        APSButton .ffclear -parent $w.ffframe.frame -text CLEAR -size small \
          -command {set apsSCRFilterFileName ""; set apsSCRPVNameFilter *; \
                      set apsSCRFilterFileDescription "" ; set apsSCRInvertFilterFile 0} \
          -contextHelp "Clears the predefined filter choice, so that no predefined filter is used."
    }

}

proc APSSCRInvertFilterFileCallback {} {
    global apsSCRInvertFilterFile apsSCRFilterFileName statusText
    if ![string length $apsSCRFilterFileName] {
        set apsSCRInvertFilterFile 0
        set statusText "Please select a predefined filter first"
        bell
    }
}

# make widget for date/time entry for start and end of
# region on interest
proc APSSCRDateTimeEntryWidget {widget args} {
    global apsSCRConfigStartYear apsSCRConfigStartMonth apsSCRConfigStartDay apsSCRConfigStartHour
    global apsSCRConfigEndYear apsSCRConfigEndMonth apsSCRConfigEndDay apsSCRConfigEndHour
    set apsSCRConfigStartYear ""
    set apsSCRConfigStartMonth ""
    set apsSCRConfigStartDay ""
    set apsSCRConfigStartHour 0
    set apsSCRConfigEndYear ""
    set apsSCRConfigEndMonth ""
    set apsSCRConfigEndDay ""
    set apsSCRConfigEndHour 24

    set parent .
    set command ""
    set relief flat
    APSStrictParseArguments {parent command relief}
    APSFrame $widget -parent $parent -label "" -relief $relief \
      -contextHelp "These widgets allow you to select the starting and ending date and time of interest."
    set w $parent$widget.frame

    APSDateTimeAdjEntry .date1 -parent $w \
      -label "Start  year, month, day, hour:" \
      -dayVariable apsSCRConfigStartDay -yearVariable apsSCRConfigStartYear \
      -monthVariable apsSCRConfigStartMonth -twoDigitYear 0 -buttonSize small \
      -hourVariable apsSCRConfigStartHour -command $command \
      -orientation horizontal -stackButtons 0
    APSDateTimeAdjEntry .date2 -parent $w \
      -label "End    year, month, day, hour:" \
      -dayVariable apsSCRConfigEndDay -yearVariable apsSCRConfigEndYear \
      -monthVariable apsSCRConfigEndMonth -twoDigitYear 0 -buttonSize small \
      -hourVariable apsSCRConfigEndHour -command $command -defaultHour 24 \
      -orientation horizontal -stackButtons 0
    APSIncrementDateVariables -offset -7 -unit day \
      -dayVariable apsSCRConfigStartDay -yearVariable apsSCRConfigStartYear \
      -monthVariable apsSCRConfigStartMonth 
    set apsSCRConfigStartHour 0
    set apsSCRConfigEndHour 24
}

proc APSSCRResolvePreferredFileChoice {args} {
    global apsSCRSystemList apsSCRRefChoiceDir
    set system ""
    set choice ""
    APSStrictParseArguments {system choice}
    if [string length $system]==0 {
        return -code error "APSSCRResolvePreferredFileChoice: No system named"
    }
    if [string length $choice]==0 {
        return -code error "APSSCRResolvePreferredFileChoice: No choice given"
    }
    if [lsearch -exact $apsSCRSystemList $system]==-1 {
        return -code error "APSSCRResolvePreferredFileChoice: invalid system $system"
    }
    set choiceFile $apsSCRRefChoiceDir/$system.sdds
    if ![file exists $choiceFile]  {
        set choiceList {"Operator Preferred" "System Manager Reference"}
        set suffixList {Preferred Reference}
    } else {
        if [catch {sdds open $choiceFile} fid] {
            return -code error "APSSCRResolvePreferredFileChoice: unable to open $choiceFile: $fid"
        }
        if [catch {sdds getColumn $fid ChoiceName} choiceList] {
            catch {sdds close $fid}
            return -code error "APSSCRResolvePreferredFileChoice: unable to read ChoiceName from $choiceFile: $choiceList"
        }
        if [catch {sdds getColumn $fid Suffix} suffixList] {
            catch {sdds close $fid}
            return -code error "APSSCRResolvePreferredFileChoice: unable to read Suffix from $choiceFile: $fileList"
        }
        catch {sdds close $fid}
    }
    set index [lsearch -exact $choiceList $choice] 
    if $index==-1 {
        return -code error "APSSCRResolvePreferredFileChoice: no choice $choice for $system"
    }
    return $system-[lindex $suffixList $index].gz
}

proc APSSCRGetPreferredChoiceList {args} {
    global apsSCRSystemList apsSCRRefChoiceDir
    set system ""
    APSStrictParseArguments {system}
    if [string length $system]==0 {
        return -code error "APSSCRGetPreferredChoiceList: No system named"
    }
    if [lsearch -exact $apsSCRSystemList $system]==-1 {
        return -code error "APSSCRGetPreferredChoiceList: invalid system $system"
    }
    set choiceFile $apsSCRRefChoiceDir/$system.sdds
    if ![file exists $choiceFile]  {
        set choiceList {"Operator Preferred" "System Manager Reference"}
    } else {
        if [catch {sdds open $choiceFile} fid] {
            return -code error "APSSCRGetPreferredChoiceList: unable to open $choiceFile: $fid"
        }
        if [catch {sdds getColumn $fid ChoiceName} choiceList] {
            catch {sdds close $fid}
            return -code error "APSSCRGetPreferredChoiceList: unable to read ChoiceName from $choiceFile: $choiceList"
        }
        catch {sdds close $fid}
    }
    return $choiceList
}

proc APSSCRGetPreferredDataLists {args} {
    global apsSCRSystemList apsSCRRefChoiceDir apsSCRAccelManagerAccount
    set system ""
    APSStrictParseArguments {system}
    if [string length $system]==0 {
        return -code error "APSSCRGetPreferredDataLists: No system named"
    }
    if [lsearch -exact $apsSCRSystemList $system]==-1 {
        return -code error "APSSCRGetPreferredDataLists: invalid system $system"
    }
    set choiceFile $apsSCRRefChoiceDir/$system.sdds
    if ![file exists $choiceFile]  {
        # default values if file doesn't exist.
        set choiceList {"System Manager Reference" "Operator Preferred"}
        set suffixList {Reference Preferred}
        set userList [list $apsSCRAccelManagerAccount($system) $apsSCRAccelManagerAccount($system)]
    } else {
        if [catch {sdds open $choiceFile} fid] {
            return -code error "APSSCRGetPreferredDataLists: unable to open $choiceFile: $fid"
        }
        if [catch {sdds getColumn $fid ChoiceName} choiceList] {
            catch {sdds close $fid}
            return -code error "APSSCRGetPreferredDataLists: unable to read ChoiceName from $choiceFile: $choiceList"
        }
        if [catch {sdds getColumn $fid Suffix} suffixList] {
            catch {sdds close $fid}
            return -code error "APSSCRGetPreferredDataLists: unable to read Suffix from $choiceFile: $suffixList"
        }
        if [catch {sdds getColumn $fid PrivilegedUsers} userList] {
            catch {sdds close $fid}
            return -code error "APSSCRGetPreferredDataLists: unable to read PrivilegedUsers from $choiceFile: $suffixList"
        }
        catch {sdds close $fid}
    }
    return [list $choiceList $suffixList $userList]
}

proc APSSCRInstallPreferredFile {args} {
    global apsSCRSystemList apsSCRRefChoiceDir apsSCRSnapDir
    set system ""
    set choice ""
    set filename ""
    APSStrictParseArguments {system choice filename}
    #if {$system=="SR" && [string compare $choice "User beam operator preferred"]==0} {
#	set link [file readlink /home/helios/oagData/SCR/snapshots/SR/SR-UserBeamPreferred.gz]
	#if {[file tail $link]=="SR-UserBeamPreferredNoP0Feedback.gz"} {
	 #   set choice "User beam operator preferred without P0 feedback"
	#} elseif {[file tail $link]=="SR-UserBeamPreferredWithP0Feedback.gz"} {
	#    set choice "User beam operator preferred with P0 feedback"
#	} else {
	#    return -code error "SR UBOP was linked to invalid link - $link!"
#	}
 #   }
    if [string length $system]==0 {
        return -code error "APSSCRInstallPreferredFile: No system named"
    }
    if [string length $choice]==0 {
        return -code error "APSSCRInstallPreferredFile: No choice given"
    }
    if [string length $filename]==0 {
        return -code error "APSSCRInstallPreferredFile: No filename given"
    }
    if [lsearch -exact $apsSCRSystemList $system]==-1 {
        return -code error "APSSCRInstallPreferredFile: invalid system $system"
    }
    if ![file exists [file join $apsSCRSnapDir $system $filename]] {
        return -code error "APSSCRInstallPreferredFile: $filename not found for system $system"
    }
    if [catch {APSGetSDDSParameter -parameter SnapshotDescription -fileName $apsSCRSnapDir/$system/$filename \
                 -page 1} Description] {
        return -code error "APSSCRInstallPreferredFile: problem reading Description: $Description"
    }
    if [string first "Routine save" $Description]!=-1 {
        return -code error "APSSCRInstallPreferredFile: can't make routine save into preferred file."
    }
    if [catch {APSSCRGetPreferredDataLists -system $system} dataLists] {
        return -code error "APSSCRInstallPreferredFile: $dataLists"
    }
# change this to use sdds load.
    set choiceList [lindex $dataLists 0]
    set suffixList [lindex $dataLists 1]
    set usersList  [lindex $dataLists 2]
    set index [lsearch -exact $choiceList $choice] 
    if $index==-1 {
        return -code error "APSSCRInstallPreferredFile: no choice $choice for $system"
    }
    set userList [split [lindex $usersList $index]]
    catch {exec whoami} userName
    if [lsearch -exact $userList $userName]==-1 {
        return -code error "APSSCRInstallPreferredFile: $userName doesn't have privileges for this operation.  The following users have the required privileges: $userList"
    }
    set linkName $apsSCRSnapDir/$system/$system-[lindex $suffixList $index].gz
    catch {file delete -force $linkName}
    if {[file exists $linkName] && [catch {file delete -force $linkName} result]} {
        return -code error "APSSCRInstallPreferredFile: unable to remove link $linkName: $result"
    }
    if [catch {exec ln -s $apsSCRSnapDir/$system/$filename $linkName} result] {
        return -code error "APSSCRInstallPreferredFile: unable to install preferred file: $result"
    }
    if [catch {APSSCRLogAction -file $filename -action "installed-as-$choice"} result] {
        return -code error "APSSCRInstallPreferredFile: $result"
    }
}

proc APSSCRAlterSnapshot {args} {
    set sourceSnapshot ""
    set replacementData ""
    set description ""
    set machine ""
    APSStrictParseArguments {sourceSnapshot replacementData description machine}

    if {![string length $sourceSnapshot] || ![file exists $sourceSnapshot]} {
        return -code error "APSSCRAlterSnapshot: invalid/nonexistent sourceSnapshot named."
    }
    if {![string length $replacementData] || ![file exists $replacementData]} {
        return -code error "APSSCRAlterSnapshot: invalid/nonexistent replacement data file named."
    }

    # get names of data columns from source snapshot and replacement data file
    # to ensure that nothing is missing from the latter
    if [catch {APSGetSDDSNames -fileName $sourceSnapshot -class column} snapshotColumns] {
        return -code error "APSSCRAlterSnapshot: $snapshotColumns"
    }
    if [catch {APSGetSDDSNames -fileName $replacementData -class column} replacementColumns] {
        return -code error "APSSCRAlterSnapshot: $replacementColumns"
    }
    
    set missingList ""
    set ignoreList [list CAError IndirectName BackupMsg RestoreMsg]
    foreach column $snapshotColumns {
        if [lsearch -exact $ignoreList $column]!=-1 continue
        if [lsearch -exact $replacementColumns $column]==-1 {
            lappend missingList $column
        }
    }
    if [llength $missingList]!=0 {
       # return -code error "APSSCRAlterSnapshot: $replacementData is missing the following columns: [join $missingList ,]"
        set answer  [APSMultipleChoice .info -name "Columns do not match" \
                      -question "APSSCRAlterSnapshot: $replacementData is missing the following columns: [join $missingList ,]\nDo you wish to continue or abort this procedure?" \
                      -labelList {Continue Abort} \
                      -returnList {Continue Abort} ]
        if {$answer=="Abort"} {
            return -code error "APSSCRAlterSnapshot: $replacementData is missing the following columns: [join $missingList ,]"
        }
    }

    set dataList [APSSCRMakeSnapshotFilename -machine $machine]
    set newFilename [lindex $dataList 0]
    set timeList [lrange $dataList 1 end]
    set time0 [format %.0lf [lindex $timeList 8]]
    set tmpRoot /tmp/[APSTmpString]
    set tmpID [APSUniqueName tmpID]
    APSAddToTmpFileList -ID $tmpID -fileList [list $tmpRoot.keep $tmpRoot.new $tmpRoot.repl]
    if [catch {exec sddsconvert $replacementData $tmpRoot.repl \
                 -retain=col,[join $snapshotColumns ,]
        exec sddsselect $sourceSnapshot $tmpRoot.repl $tmpRoot.keep \
                 -match=ControlName -invert -reuse=rows
        exec sddscombine $tmpRoot.keep $tmpRoot.repl -pipe=out -merge \
                 -delete=column,CAError \
                 | sddsprocess -pipe=in $tmpRoot.new \
                 "-reprint=param,SnapshotDescription,[APSMakeSafeQualifierString $description]" \
                 "-redefine=param,Time,$time0" \
                 "-reprint=param,SnapshotFilename,$newFilename"
        file delete $tmpRoot.keep } result] {
        APSDeleteTmpFileList -ID $tmpID
        return -code error "APSSCRAlterSnapshot: $result"
    }

    set snaproot  [file tail [file rootname $sourceSnapshot]]
    set snapexten [file extension $sourceSnapshot]
    set snapdir   [file dirname $sourceSnapshot]/
    if [string compare $snapdir ./]==0 {
        set snapdir ""
    }
    set newroot   [file tail [file rootname $newFilename]]
    set newexten  .gz
    set waveformFile ${snapdir}${snaproot}.waveform.gz
    if [catch {exec gzip -c $tmpRoot.new > $snapdir$newroot.gz
        if {[file exists ${snapdir}/collapsed]} {
            exec sddscollapse $snapdir$newroot.gz ${snapdir}/collapsed/c$newroot
        } else {
            exec sddscollapse $snapdir$newroot.gz ${snapdir}c$newroot
        }
        if {[file exists ${snapdir}/categories/categ$snaproot]} {
            exec cp ${snapdir}/categories/categ$snaproot ${snapdir}/categories/categ$newroot 
        } else {
            exec cp ${snapdir}categ$snaproot ${snapdir}categ$newroot 
        }
        if {[file exists ${snapdir}/beamlines/beam$snaproot]} {
            exec cp ${snapdir}/beamlines/beam$snaproot ${snapdir}/beamlines/beam$newroot
        } else {
            exec cp ${snapdir}beam$snaproot ${snapdir}beam$newroot
        }
    } result] {
        APSDeleteTmpFileList -ID $tmpID
        return -code error "APSSCRAlterSnapshot: $result"
    }
    if [file exist $waveformFile] {
        if [catch {exec cp $waveformFile $snapdir$newroot.waveform.gz} result] {
            return -code error "APSSCRAlterSnapshot2: $result"
        }
    }
    APSDeleteTmpFileList -ID $tmpID
    return $newroot.gz
}

proc APSAlterSCRRequestFile {args} {
    set input ""
    set output ""
    set add ""
    set remove ""
    set rename ""
    set usage "arguments: -input <filename> -output <filename> -add <filename> -remove <filename> -rename <filename>"
    if {[APSStrictParseArguments {input output add remove rename}] || \
          ![string length $input] || ![string length $output] || \
          (![string length $add] && ![string length $remove] \
             && ![string length $rename])} {
        return -code error "APSAlterSCRRequestFile: $usage"
    }
    if ![file exists $input] {
        return -code error "APSAlterSCRRequestFile: Not found: $input"
    }
    if [file exists $output] {
        return -code error "APSAlterSCRRequestFile: Already exists: $output"
    }
    set tmpInput /tmp/[APSTmpString]
    set tmpID [APSUniqueName tmpID]
    APSAddToTmpFileList -ID $tmpID -fileList $tmpInput
    if [catch {exec cp $input $tmpInput} result] {
        APSDeleteTmpFileList -ID $tmpID
        return -code error "APSAlterSCRRequestFile: $result"
    }
    if [string length $rename] {
        set tmpOutput /tmp/[APSTmpString]
        APSAddToTmpFileList -ID $tmpID -fileList $tmpOutput
        if [catch {APSRenameInSCRRequestFile \
                     -input $tmpInput -output $tmpOutput \
                     -rename $rename} result] {
            APSDeleteTmpFileList -ID $tmpID
            return -code error "APSAlterSCRRequestFile: $result"
        }
        set tmpInput $tmpOutput
    }
    if [string length $remove] {
        set tmpOutput /tmp/[APSTmpString]
        APSAddToTmpFileList -ID $tmpID -fileList $tmpOutput
        if [catch {APSRemoveFromSCRRequestFile \
                     -input $tmpInput -output $tmpOutput \
                     -remove $remove} result] {
            APSDeleteTmpFileList -ID $tmpID
            return -code error "APSAlterSCRRequestFile: $result"
        }
        set tmpInput $tmpOutput
    }
    if [string length $add] {
        set tmpOutput /tmp/[APSTmpString]
        APSAddToTmpFileList -ID $tmpID -fileList $tmpOutput
        if [catch {APSAddToSCRRequestFile -input $tmpInput -output $tmpOutput \
                     -add $add} result] {
            APSDeleteTmpFileList -ID $tmpID
            return -code error "APSAlterSCRRequestFile: $result"
        }
        set tmpInput $tmpOutput
    }
    if [file exists $tmpOutput] {
        if [catch {exec cp $tmpOutput $output} result] {
            APSDeleteTmpFileList -ID $tmpID
            return -code error "APSAlterSCRRequestFile: $result"
        }
    } else {
        APSDeleteTmpFileList -ID $tmpID
        return -code error "APSAlterSCRRequestFile: $tmpOutput not found"
    }
}

proc APSAddToSCRRequestFile {args} {
    # Adds data from file $add to file $input and puts result in $output.
    # If ControlNames in $add match any in $input, then those in $input
    # are deleted.

    set input ""
    set output ""
    set add ""
    APSStrictParseArguments {input output add} 

    if {![string length $input] || ![string length $output] || \
          ![string length $add]} {
        return -code error "APSAddToSCRRequestFile: arguments:  -input <filename> -output <filename> -add <filename>"
    }
    if ![file exists $input] {
        return -code error "APSAddToSCRRequestFile: not found: $input"
    }
    if [file exists $output] {
        return -code error "APSAddToSCRRequestFile: already exists: $output"
    }
    if ![file exists $add] {
        return -code error "APSAddToSCRRequestFile: not found: $add"
    }
    
    if [catch {APSGetSDDSNames -fileName $input -class column} inputColumns] {
        return -code error "APSAddToSCRRequestFile: $inputColumns"
    }
    if [catch {APSGetSDDSNames -fileName $add -class column} addColumns] {
        return -code error "APSAddToSCRRequestFile: $addColumns"
    }
    foreach item $inputColumns {
        if [lsearch -exact $addColumns $item]==-1 {
            return -code error "APSAddToSCRRequestFile: $item not in $add but in $input"
        }
    }
    set tmpRoot /tmp/[APSTmpString]
    set tmpID [APSUniqueName tmpID]
    APSAddToTmpFileList -ID $tmpID -fileList \
      [list $tmpRoot.add $tmpRoot.keep]
    if [catch {exec sddsconvert $add $tmpRoot.add \
                 -retain=column,[join $inputColumns ,]
        exec sddsselect $input $tmpRoot.add $tmpRoot.keep \
                 -match=ControlName -invert -reuse=page,row
        exec sddscombine $tmpRoot.add $tmpRoot.keep -merge $output} result] {
        APSDeleteTmpFileList -ID $tmpID
        return -code error "APSAddToSCRRequestFile: $result"
    }
    catch {eval file delete $tmpRoot.add $tmpRoot.keep}
    if ![file exists $output] {
        APSDeleteTmpFileList -ID $tmpID
        return -code error "APSAddToSCRRequestFile: not found after processing!"
    }
    APSDeleteTmpFileList -ID $tmpID
}

proc APSRemoveFromSCRRequestFile {args} {
    set input ""
    set output ""
    set remove ""
    set usage "arguments: -input <filename> -output <filename> <filename> -remove <filename>"
    if {[APSStrictParseArguments {input output remove}] || \
          ![string length $input] || ![string length $output] || \
          ![string length $remove]} {
        return -code error "APSRemoveFromSCRRequestFile: $usage"
    }
    if ![file exists $input] {
        return -code error "APSRemoveFromSCRRequestFile: Not found: $input"
    }
    if [file exists $output] {
        return -code error "APSRemoveFromSCRRequestFile: Already exists: $output"
    }
    if ![file exists $remove] {
        return -code error "APSRemoveFromSCRRequestFile: not found: $add"
    }
    if [catch {exec sddsselect $input $remove $output -match=ControlName -invert \
                 -reuse=page,row} result] {
        return -code error "APSRemoveFromSCRRequestFile: $result"
    }
}

proc APSRenameInSCRRequestFile {args} {
    set input ""
    set output ""
    set rename ""
    set usage "arguments: -input <filename> -output <filename> <filename> -rename <filename>"
    if {[APSStrictParseArguments {input output rename}] || \
          ![string length $input] || ![string length $output] || \
          ![string length $rename]} {
        return -code error "APSRenameFromSCRRequestFile: $usage"
    }
    if ![file exists $input] {
        return -code error "APSRenameFromSCRRequestFile: Not found: $input"
    }
    if [file exists $output] {
        return -code error "APSRenameFromSCRRequestFile: Already exists: $output"
    }
    if ![file exists $rename] {
        return -code error "APSRenameFromSCRRequestFile: not found: $add"
    }
    set tmpRoot /tmp/[APSTmpString]
    set tmpID [APSUniqueName tmpID]
    APSAddToTmpFileList -ID $tmpID -fileList \
      [list $tmpRoot.add $tmpRoot.renamed]
    if [catch {exec sddsselect $input $rename $tmpRoot.keep -reuse=page,row \
                 -match=ControlName=OldControlName -invert} result] {
        APSDeleteTmpFileList -ID $tmpID
        return -code error "APSRenameFromSCRRequestFile: $result"
    }
    if [catch {exec sddsxref -nowarning $input $rename -pipe=out -reuse=page,row \
                 -match=ControlName=OldControlName -take=NewControlName \
                 | sddsconvert -pipe=in $tmpRoot.renamed \
                 -delete=column,ControlName \
                 -rename=column,NewControlName=ControlName} result] {
        APSDeleteTmpFileList -ID $tmpID
        return -code error "APSRenameFromSCRRequestFile: $result"
    }
    if [catch {exec sddscombine $tmpRoot.keep $tmpRoot.renamed -merge \
                 $output -overwrite} result] {
        APSDeleteTmpFileList -ID $tmpID
        return -code error "APSRenameFromSCRRequestFile: $result"
    }

}

# This procedure takes a request file and makes another SDDS file
# containing one row for each unique value of a specified column.
# Used for making category and beamline lists after creating a
# new request file.

proc APSMakeSCRCategoryFiles {args} {
    set input ""
    APSStrictParseArguments {input} 
    if {![string length $input] || ![file exists $input]} {
        return -code error "APSMakeSCRCategoryFiles: input file not given or nonexistent: $input"
    }
    set mtime0 [file mtime $input]
    set extensionList {categories beamlines}
    set columnList {Category Beamline}
    set index -1 
    foreach column $columnList {
        incr index 
        set output $input.[lindex $extensionList $index]
        if {![file exists $output] || \
              [file mtime $output]<=$mtime0} {
            if [catch {exec sddsbreak -rowlimit=100 $input -pipe=out \
                         | sddssort -pipe -column=$column -unique \
                         | sddscombine -merge -pipe \
                         | sddssort -pipe=in -column=$column -unique $output} result] {
                return -code error "APSMakeSCRCategoryFiles: $result"
            }
        }
    }
}

proc APSScaleSnapshot {args} {
    set input ""
    set output ""
    set factor 1
    if [APSStrictParseArguments {input output factor}] {
        return -code error "APSScaleSnapshot: unknown options given"
    }

    if ![string length $input] {
        return -code error "APSScaleSnapshot: provide input file name"
    }
    if ![string length $output] {
        return -code error "APSScaleSnapshot: provide output file name"
    }
    if ![file exists $input] {
        return -code error "APSScaleSnapshot: $input not found"
    }
    if [catch {APSGetSDDSNames -fileName $input -class column} inputColumnList] {
        return -code error "APSScaleSnapshot: $inputColumnList"
    }
    foreach item {ControlName ValueString} {
        if [lsearch -exact $inputColumnList $item]==-1 {
            return -code error "APSScaleSnapshot: $item is missing from $input"
        }
    }
    if [lsearch -exact $inputColumnList IsNumerical]!=-1 {
        lappend selectOption -match=column,IsNumerical=+y
    }
    lappend selectOption -numberTest=column,ValueString,strict

    set tmpRoot /tmp/[APSTmpString]
    set ID [APSUniqueName tmpID]
    APSAddToTmpFileList -ID $ID -fileList [list $tmpRoot.1 $tmpRoot.2 ]
    if [catch {eval exec sddsconvert $input -pipe=out \
                 -delete=column,Value,ValueString2 \
                 | sddsprocess -pipe $selectOption \
                 -scan=column,Value,ValueString,%lf \
                 {"-redefine=column,Value,Value $factor *"} \
                 -print=column,ValueString2,%.15e,Value \
                 | sddsconvert -pipe=in $tmpRoot.1 \
                 -delete=column,Value,ValueString \
                 -rename=column,ValueString2=ValueString 
        exec sddsselect $input $tmpRoot.1 -match=ControlName -invert $tmpRoot.2 
        exec sddscombine $tmpRoot.1 $tmpRoot.2 $output -merge -overWrite } result] {
        APSDeleteTmpFileList -ID $ID
        return -code error "APSScaleSnapshot: $result"
    }
    APSDeleteTmpFileList -ID $ID
}

proc CompareRequestOrMonitorFiles {args} {
    set lastFile ""
    set newFile ""
    set root ""
    set type req
    set install 0
    APSStrictParseArguments {lastFile newFile root type install}
    
    if {![file exist $newFile] || ![file exist $lastFile]} {
        return
    }
    if $install {
        switch $type {
            req {
                set pvsAddedFile $root.added
                set pvsRemovedFile $root.removed
            }
            mon {
                set pvsAddedFile PVsAdded
                set pvsRemovedFile PVsRemoved
            }
            default {
                return -code error "Invalid monitor type, should be req or mon!"
            }
        }
    } else {
        set pvsAddedFile /tmp/[APSTmpString].$root.added
        set pvsRemovedFile /tmp/[APSTmpString].$root.removed
    }
    
    if [catch {exec sddsselect $newFile $lastFile $pvsAddedFile \
                   -match=ControlName -invert -nowarnings
        exec sddsselect $lastFile $newFile $pvsRemovedFile \
                   -match=ControlName -invert -nowarnings } result] {
        return -code error $result
    }
    
    set rowsAdded [exec sdds2stream -rows=bar $pvsAddedFile]
    set rowsRemoved [exec sdds2stream -rows=bar $pvsRemovedFile]
    if {$rowsAdded || $rowsRemoved} {
        set message "New file $newFile differs from last file $lastFile.\n"
        append message "New  file: [exec sdds2stream -rows $newFile]\n"
        append message "Last file: [exec sdds2stream -rows $lastFile]\n"
        append message "PVs added: $rowsAdded rows.\n"
        append message "PVs removed: $rowsRemoved rows.\n"
        if $rowsAdded {
            append message "Following PVs were added...\n"
            append message "[exec sdds2stream -col=ControlName $pvsAddedFile]\n"
        }
        if $rowsRemoved {
            append message "\nFollowing PVs were removed...\n"
            append message "[exec sdds2stream -col=ControlName $pvsRemovedFile]\n"
        }
        return -code error $message
    }
}

proc APSSCRRampSRMagnets {args} {
    set doQuads 0
    set doSextoples 1
    set SCRFile ""
    set machine SR
    global apsUpgrade
    APSStrictParseArguments {doQuads doSextoples SCRFile machine}
    set matchOpt ""
    
    set dir /home/helios/oagData/SCR/snapshots/$machine
    if {!$doQuads && !$doSextoples} {
        return -code error "APSSRRampMagnets(1): At least one of the quads or sextoples has to be selected for ramp SR magnets!"
    }
    if $doQuads {
        set matchOpt -match=column,ControlName=S*:Q*:CurrentAO
    }
    if $doSextoples {
        if [string length $matchOpt] {
            append matchOpt ,ControlName=S*:S?:CurrentAO,|
        } else {
            set matchOpt -match=column,ControlName=S*:S?:CurrentAO
        }
    }
    if {$apsUpgrade} {
        append matchOpt " -match=column,Beamline=SR -match=column,Category=MainPS -match=column,IsProtected=+n"
    } else {
        append matchOpt " -match=column,Beamline=SR -match=column,Category=MainPS -match=column,IsProtected=+n  -match=column,ControlType=pv"
    }
    set tmpfile /tmp/[APSTmpString]
    if [catch {eval exec sddsprocess $dir/$SCRFile $tmpfile $matchOpt \
             \"-match=col,ControlName=,!\" \"-match=col,ValueString=,!\" } result] {
        return -code error "APSSRRampMagnets(2): $result"
    }
    
    set restoreTimeStamp "[clock format [clock seconds]]"
    if [catch {APSSCRLogAction -file $SCRFile -action restore-start} result] {
        return -code error "APSSRRampMagnets(2a): $result"
    }
    
    if [catch {APSRampToSnapshot -initDialog 1 \
                 -logFile $tmpfile.log -fileName $tmpfile} result] {
        return -code error "APSSRRampMagnets(3 - APSRampToSnapshot): $result"
    } 
    if [catch {APSSCRLogAction -file $dir/$SCRFile -action restore-end} result] {
        return -code error "APSSRRampMagnets(3a - APSSCRLogAction): $result"
    }
    #track restored file
    if [catch {APSSCRTrackRestoreFile -sourceFile $SCRFile \
                 -machine $machine -restoreFile $tmpfile \
                 -timeStamp $restoreTimeStamp} result] {
        return -code error "APSSRRampMagnets(4 - APSSCRTrackRestoredFile): $result"
    }
}

proc APSSCRTrackRestoreFile {args} {
    set sourceFile ""
    set machine ""
    set restoreFile ""
    set timeStamp ""
    APSStrictParseArguments {sourceFile machine restoreFile timeStamp}
    global env 

    set restoreDir /home/helios/oagData/SCR/restoreTracking
    if {![string length $restoreFile] || ![string length $sourceFile] } {
        return
    }
    set suffix [lindex [APSSCRMakeSnapshotFilename -machine $machine] 0]
    regexp "${machine}(.*)" $suffix a suffix
    if ![file exist $restoreDir/$machine] {
        exec mkdir $restoreDir/$machine
        #catch {exec setfacl -m mask:rwx $restoreDir/$machine}
    }
    if [regexp {(.*).gz} $sourceFile a b] {
        set prefix $b
    } else {
        set prefix $sourceFile
    }
    set filename ${prefix}_${suffix}
    
    if [catch {exec sddsprocess $restoreFile \
                 "-reprint=par,System,$machine" \
                 "-reprint=par,SourceFile,$sourceFile" \
                 "-reprint=par,User,$env(USER)" \
                 "-reprint=par,TimeStamp,$timeStamp" -pipe=out \
                 | gzip -c > $restoreDir/${machine}/$filename.gz } result] {
        return -code error "Error gzipping file: $result"
    }
}


# Adds an SCR dialog to a window.  Suitable for use with PEM init dialogs

proc APSAddSCRDialog {widget args} {
    set parent ""
    set label ""
    set arrayName ""
    set daysPast 7
    set system LINAC
    set defaultFile ""
    set description ""
    set PreferredOnly 0
    set searchDescription *
    APSStrictParseArguments {parent label arrayName daysPast system defaultFile description PreferredOnly searchDescription}
    if {[llength $label] == 0} {
        set label "Choose $system SCR file"
    }
    APSFrame $widget -parent $parent -label $label -packOption "-expand 1 -fill both"
    set w $parent$widget.frame
    global $arrayName
    global apsSCRConfigStartYear apsSCRConfigStartMonth apsSCRConfigStartDay apsSCRConfigDescriptionSearchString
    global apsSCRConfigEndYear apsSCRConfigEndMonth apsSCRConfigEndDay apsSCRSystem
    set apsSCRConfigDescriptionSearchString "$searchDescription"
    set apsSCRSystem $system
    if !$PreferredOnly {
        APSSCRDateTimeEntryWidget .dtew -parent $w 
    }
    APSSCRDefineVariables
    APSDateBreakDown -dayVariable apsSCRConfigStartDay \
      -yearVariable apsSCRConfigStartYear -monthVariable apsSCRConfigStartMonth \
      -twoDigitYear 0 -leadingZeros 0
    APSDateBreakDown -dayVariable apsSCRConfigEndDay \
      -yearVariable apsSCRConfigEndYear -monthVariable apsSCRConfigEndMonth \
      -twoDigitYear 0 -leadingZeros 0
    if $daysPast {
        APSIncrementDateVariables -offset [expr -1*abs($daysPast)] -unit day \
          -dayVariable apsSCRConfigStartDay -yearVariable apsSCRConfigStartYear \
          -monthVariable apsSCRConfigStartMonth 
    }
    
    APSFrame .choice -parent $w -label "Snapshot choice (${system})"
    set w1 $w.choice.frame
    set ${arrayName}(ShortFilename) $defaultFile
    set ${arrayName}(Description) $description
    APSLabeledOutput .filename -parent $w1 -label "Filename" \
      -textVariable ${arrayName}(ShortFilename) -width 100
    APSLabeledOutput .description -parent $w1 -label "Description" \
      -textVariable ${arrayName}(Description) -width 100
    APSLabeledEntry .descript -parent $w1 -label "Description search string: " \
      -textVariable apsSCRConfigDescriptionSearchString  -packOption "-side top" -width 40 \
      -contextHelp \
      "Enter a string to use in searching for files by matching the description given by the operator who saved the snapshot." \
      -packOption "-side left -anchor n"
    
    if !$PreferredOnly {
        APSButton .refresh -parent $w1 -text Refresh \
          -command \
          "APSListSCRMatchSetArray -machine $system -height 4 \
      -startYear \$apsSCRConfigStartYear -startMonth \$apsSCRConfigStartMonth \
      -startDay \$apsSCRConfigStartDay \
      -endYear \$apsSCRConfigEndYear -endMonth \$apsSCRConfigEndMonth \
      -endDay \$apsSCRConfigEndDay \
      -descriptionFragment \$apsSCRConfigDescriptionSearchString -parent $w \
      -statusCallback APSNoOp -callback APSAddSCRDialogCallback \
      -arrayName $arrayName -routine 0 -PreferredOnly $PreferredOnly -listOrder decreasing" 
       
        APSButton .clear -parent $w1 -text Clear \
          -command "set ${arrayName}(ShortFilename) {}; set ${arrayName}(Description) {}; set ${arrayName}(Filename) {};set apsSCRConfigDescriptionSearchString *"
    }
    
    APSListSCRMatchSetArray -machine $system -height 3 \
      -startYear $apsSCRConfigStartYear -startMonth $apsSCRConfigStartMonth \
      -startDay $apsSCRConfigStartDay \
      -endYear $apsSCRConfigEndYear -endMonth $apsSCRConfigEndMonth \
      -endDay $apsSCRConfigEndDay \
      -descriptionFragment $apsSCRConfigDescriptionSearchString -parent $w -statusCallback "APSNoOp" \
      -callback APSAddSCRDialogCallback -arrayName $arrayName \
      -PreferredOnly $PreferredOnly \
      -routine 0 -listOrder decreasing

    set ${arrayName}(ShortFilename) $defaultFile
    set ${arrayName}(PreferredOnly) $PreferredOnly
    return
}

proc APSAddSCRDialogCallback {arrayName machine} {
    global $arrayName apsSCRSnapDir status 
    if [set ${arrayName}(FileNotFound)] {
        set status "File not found!"
        update
        return
    }
    set name [set ${arrayName}(Filename)]
    set descrip [set ${arrayName}(Description)]
    set snapDir $apsSCRSnapDir/$machine
    if ![file exists $snapDir/$name] {
        if [file exists $snapDir/${name}.gz] {
            set name ${name}.gz
        } else {
            set status "File not found!"
            update
            return
        }
    }
    set ${arrayName}(ShortFilename) $name
    set ${arrayName}(Filename) $snapDir/$name
}

proc APSMpGenericPSArrayQuery {args} {
    set arrayName ""
    set operation ""
    set invert 0
    APSStrictParseArguments {arrayName operation invert}
    global $arrayName

    set chosenList ""
    # <operation>,DeviceList is the list of devices in a group
    # supporting an operation
    # <operation>,DeviceVariableList is the list of Tcl variables
    # associated with checkbuttons for users to select the operation for
    # a device

    foreach device [set ${arrayName}($operation,DeviceList)] \
      choiceVar [set ${arrayName}($operation,DeviceVariableList)] {
          if [set $choiceVar]!=$invert {
              lappend chosenList $device
          }
      }
    return $chosenList
}
