LDAS logo
TclDOC logo

The frame.tcl Script

Modification Date: 11/26/2008

Table of Procedures

red ball frame::0xdb
red ball frame::addChans
red ball frame::analyzeFilename
red ball frame::analyzeOfficialFilename
red ball frame::analyzeQueryElement
red ball frame::attributes
red ball frame::bgUpdateDeviceIOConfiguration
red ball frame::callbackRDS
red ball frame::chanlist
red ball frame::channelsToIfos
red ball frame::chanp2ilwdp
red ball frame::collectElements
red ball frame::createIlwdPrototypeFrame
red ball frame::createRDSFrames
red ball frame::createRDSParseChannels
red ball frame::defunctJobs
red ball frame::destructElementWrap
red ball frame::destructImage
red ball frame::destructPointers
red ball frame::detectorAndHistory
red ball frame::dictionary
red ball frame::expandDataQuery
red ball frame::file2ptr
red ball frame::filenameCalculator
red ball frame::fixTimesAndFrames
red ball frame::framep2ilwdp
red ball frame::fullRehash
red ball frame::getChanp
red ball frame::getImage
red ball frame::getNumber
red ball frame::getOptArray
red ball frame::globChanNames
red ball frame::graftPointers
red ball frame::ilwdCompressionMetadata
red ball frame::ilwdp2framep
red ball frame::inFrame
red ball frame::init
red ball frame::inputFileFlagHandler
red ball frame::killJob
red ball frame::killJobReattach
red ball frame::latest
red ball frame::managePointers
red ball frame::matchTimesAndFrames
red ball frame::md5sumOutputDir
red ball frame::mergePointers
red ball frame::method2ptr
red ball frame::newJob
red ball frame::newJobAbort
red ball frame::newJobCallback
red ball frame::next
red ball frame::notifyBadFrames
red ball frame::openFrameFileCallback
red ball frame::openFrameFileThread
red ball frame::open_cat
red ball frame::open_d
red ball frame::open_r
red ball frame::open_u
red ball frame::open_w
red ball frame::output
red ball frame::outputFileFlagHandler
red ball frame::outputFrames
red ball frame::outputIlwd
red ball frame::outputLigolw
red ball frame::outputURL
red ball frame::parseChannelSlice
red ball frame::parseFrameQuery
red ball frame::parseRDSFramequery
red ball frame::prunePointers
red ball frame::rationalizedFilenameData
red ball frame::rdsDirectory
red ball frame::reapOrphanThread
red ball frame::reattachMsg
red ball frame::registerTid
red ball frame::registry
red ball frame::resampleOneAdc
red ball frame::restrictedUserDirs
red ball frame::runNewJob
red ball frame::sanity
red ball frame::setImage
red ball frame::timeData
red ball frame::timeWindow
red ball frame::unregisterTid
red ball frame::unrestrictedUserDirs
red ball frame::updateDeviceIOConfiguration
red ball frame::waitForRDSData
red ball frame::writeFile
red ball frame::writeFileCallback

frame.tcl Version 1.0 Wraps the frameAPI.so and the genericAPI.so and the genericAPI.tcl for use by the frame API.
frame::cache() is a list of directories and the gps timestamps of the first and last frames in those directories.
frame.tcl requires two other frame API specific Tcl source files:

  1. frame_concat.tcl
  2. frame_output.tcl
set ::RCS_ID_frametcl {$Id: frame.tcl,v 1.667 2008/10/16 21:30:04 mlei Exp $}
set ::RCS_ID_frametcl [ string trim $::RCS_ID_frametcl "\$" ]
package provide frame 1.0
package require frameAPI
#package require frame_cache
package require frame_output
package require frame_concat
set ::last_job_start_time [ clock seconds ]
namespace eval frame {
   set errlvl 1
   ;## the current interferometer id
   set ifoid  {}
   ;## images are pairs of jobid and framep
   set images [ list ]
   ;## the list of pending jobs
   set newjobqueue [ list ]
   ;## options are defined with a name, a regex pattern
   ;## which matches them, and a default value if applicable.
   ;## the variables will not get substituted at this point.
   ;## note that the REQUIRED options have no default and
   ;## will raise exceptions if not provided.
   set options {
       -interferometers {.*} {}
       -outputformat    {.*} {ilwd ascii}
       -returnprotocol  {.*} {}
       -concatenate     {^(0|\-?1)$} 0
       -framequery      {.+}     {}
       -targetapi       {.*}     {}
       -allowgaps       {^[01]$} 0
       -frametarget     {.*}     {}
       -jobid           {\d+}    $jobid
   }
   ;## atoms are framequery string fragments which must be
   ;## properly capitalised for calculating the appropriate
   ;## accessor method for the framequery.
   ;## all dataqueries are forced to lowercase and then
   ;## substituted with thse patterns.
   ;## ORDER MATTERS!!
   set atoms [ list \
       FrameH Frame Proc DataQuality Data List Sim Ser Adc Dt \
       NAuxParam AuxParam AuxParamNames \
       Raw Channel Comment Group Valid History FShift Number \
       Num Name Sample Rate Arm Xazimuth Yazimuth Elevation Phase \
       Detector Latitude Longitude Xaltitude Xmidpoint Yaltitude \
       Ymidpoint Time Local Msg Message Severity EventStatus Event Statistics \
       Trigger Test End Start Inputs OffsetN OffsetS Probability \
       Status Offset Amplitude After Before Stat Representation \
       Version Strain Summary Trig ULeapS Daq Binary GTime Quality \
       Attribute Units QaBitList Slope NBits Group Bias Aux Run \
       BW SubType Type FRange TRange \
       NParam Parameters ParameterNames \
   ]
 }

§   §   §

Name: frame::destructElementWrap

Description:
destructElement and then remove swig names
Parameters: Usage:

Comments:

proc frame::destructElementWrap { ptr } {
	if	{ [ catch {
    	destructElement $ptr
       	if  { [ string length [ info commands $ptr ] ] } {
            # addLogEntry "rename $ptr [ info commands $ptr ] to null" purple
            rename $ptr {}
        }
  	} err ] } {
    	return -code error $err
    }
}

§   §   §

Name: frame::init

Description:
Entry point for the LDASgwap script to initialise the frame API. Initialises the frame name cache, and starts the bgLoop (see
genericAPI.tcl) controlled frame cache update loop.

Parameters: Usage:

Comments:

It was possible to initialise the frame API on a machine which automounted the frame server, using about %5 of the cpu, with an average load of 2 with nothing else running.
proc frame::init { } {
     if { [ catch {
        if { ! [ info exists ::DEBUG_QUERY_EXPANSION ] } {
           set ::DEBUG_QUERY_EXPANSION 0
        } elseif { [ string equal false [ string tolower \
           $::DEBUG_QUERY_EXPANSION ] ] } {
           set ::DEBUG_QUERY_EXPANSION 0
        } elseif { ! [ regexp {^[01]$} $::DEBUG_QUERY_EXPANSION ] } {
           set ::DEBUG_QUERY_EXPANSION 1
        }
        ;## ::DEBUG_FILE2PTR must follow the same rules.
        if { ! [ info exists ::DEBUG_FILE2PTR ] } {
           set ::DEBUG_FILE2PTR 0
        } elseif { [ string equal false [ string tolower \
           $::DEBUG_FILE2PTR ] ] } {
           set ::DEBUG_FILE2PTR 0
        } elseif { ! [ regexp {^[01]$} $::DEBUG_FILE2PTR ] } {
           set ::DEBUG_FILE2PTR 1
        }
        ;## ::DEBUG_FILECACHE must follow the same rules.
        if { ! [ info exists ::DEBUG_FILECACHE ] } {
           set ::DEBUG_FILECACHE 0
        } elseif { [ string equal false [ string tolower \
           $::DEBUG_FILECACHE ] ] } {
           set ::DEBUG_FILECACHE 0
        } elseif { ! [ regexp {^[01]$} $::DEBUG_FILECACHE ] } {
           set ::DEBUG_FILECACHE 1
        }
        if { ! [ info exists ::FRAME_NEW_JOB_RATE ] } {
           set ::FRAME_NEW_JOB_RATE 2
        }
        set msg "::FRAME_NEW_JOB_RATE is set to "
        append msg "$::FRAME_NEW_JOB_RATE seconds. "
        append msg "(1 to 15 seconds is usual, default "
        append msg "is 2)."
        addLogEntry $msg green
		# if	{ ! [ info exist ::DUMPDIR ] } {
        #	set ::DUMPDIR /ldas_outgoing/test/frame_params
        #}
        # if	{ ! [ file exist $::DUMPDIR ] } {
        #   	file mkdir $::DUMPDIR
        #}
        # addLogEntry "job parameters are dumped in $::DUMPDIR" purple
        if { [ catch {
           #frame::sanity STARTUP0
        } err ] } {
           addLogEntry "Frame API init error: $err" 2
           puts stderr "Frame API init error: $err"
        }
       	
        if	{ ![ info exist ::REAP_THREAD_DELAY ] } {
        	set ::REAP_THREAD_DELAY 5000
        }
        if { ! [ info exists ::DEVICE_IO_CONFIGURATION ] } {
	    set ::DEVICE_IO_CONFIGURATION [ list ]
        }
        if  { ! [ info exists ::DELAY_RECV_FRAMES_FROM_DISKCACHE_SECS ] } {
	        set ::DELAY_RECV_FRAMES_FROM_DISKCACHE_SECS 120
        }
    	if	{ [ regexp {8.4} $::tcl_version ] } {
    		trace add variable ::DEVICE_IO_CONFIGURATION { write } \
	    	frame::updateDeviceIOConfiguration
        	trace add variable ::DEVICE_IO_CONFIGURATION { unset } \
	    	frame::updateDeviceIOConfiguration
    	} else {
			trace variable ::DEVICE_IO_CONFIGURATION wu \
	    	frame::updateDeviceIOConfiguration
        }
	    frame::updateDeviceIOConfiguration
        bgLoop framejobqueue frame::newJob 3
        bgLoop defunctjobs frame::defunctJobs 300
        bgLoop deviceioconfig frame::bgUpdateDeviceIOConfiguration 300
     } err ] } {
        addLogEntry "Frame API init error: $err" 2
        puts stderr "Frame API init error: $err"
     }
}

§   §   §

Name: frame::dictionary

Description:
Read the frame dictionary and creates an associative array which holds all of the data types for all of the frame structures.
The array is named ::frame::frame()

Parameters: Usage:

Comments:

As of today (12/18/00) there are 208 distinct objects, any of which may be a ptr to an array of the objects.
An ADC channel, for instance, is an element of an array of an ::frame::frame(FrRawData,firstAdc) and ::frame::frame(FrAdcData,next) which reference an array of type PTR_STRUCT(FrAdcData *).
proc frame::dictionary { } {
     if { [ catch {
        set seqpt "getFrameDictionary:"
        set dic [ getFrameDictionary ]
        array set ::frame::frame $dic
        foreach struct [ array names ::frame::frame ] {
           foreach [ list name type ] [ set ::frame::frame($struct) ] {
              set ::frame::frame($struct,$name) $type
           }
           unset ::frame::frame($struct)
        }
     } err ] } {
        return -code error "[ myName ]:$seqpt $err"
     }
     return [ llength [ array names ::frame::frame ] ]
}

§   §   §

Name: frame::parseFrameQuery

Description:
Parse the -framequery option into multiple associated requests if it looks like a compound query.
The -frames, -times and -ifos options are NOT used when a compound -framequery is detected.
Compound arguments are always formatted as a list of 4 elements with empty elements represented as "{}".
The elements must be presented in correct order:
{ types ifos frames times query }
Parameters: Usage:
  supports -framequery options in several formats:

-framequery { F {H L} {} 600000000 Adc(0) } -framequery { { F H {} 600000000 Adc(0) } { R L {} 600000000 Adc(100) } }
Comments:
proc frame::parseFrameQuery { jobid args } {
     if { [ catch {
        set retval [ list ]
        if { [ llength $args ] == 1 } {
           ;## Determine whether this is a simple query
           ;## or a complex query
           set qlist [ lindex $args 0 ]
           if { [ llength $qlist ] == 5 && \
                [ llength [ lindex $qlist 0 ] ] != 5 } {
              ;## This is a simple query
           } else {
              ;## This is a complex query,
              ;## so modify args to be a list of
              ;## the component simple-queries
              set args $qlist
           }
        }
        foreach arg $args {
           if { ! [ string length $arg ] } { continue }
           foreach [ list types ifos times frames channels ] \
                   [ frame::analyzeQueryElement $jobid $arg ] { break }
           lappend retval [ list $types $ifos $times $frames $channels ]
        }
        if { $::DEBUG_QUERY_EXPANSION } {
           addLogEntry "(types ifos times frames chans): '$retval'" purple
        }
     } err ] } {
        return -code error "[ myName ]: $err"
     }
     return $retval
}

§   §   §

Name: frame::analyzeQueryElement

Description:
Calculates the frame files and object list for a -framequery option.

Parameters: Usage:

Comments:

proc frame::analyzeQueryElement { jobid args } {
     if { [ llength $args ] == 1 } {
        set args [ lindex $args 0 ]
     }
     if { [ catch {
        set types           [ list ]
        set frames          [ list ]
        set channels        [ list ]
        set interferometers [ list ]
        set times           [ list ]
        foreach [ list typs ifos frms tims ques ] $args { break }
        if { ! [ string length $typs ] } { set typs R }
        if { ! [ string length $ifos ] } { set ifos 0 }
        set typ_length [ llength $typs ]
        set ifo_length [ llength $ifos ]
        set frm_length [ llength $frms ]
        set tim_length [ llength $tims ]
        set que_length [ llength $ques ]
        foreach frame $frms {
           array set temp [ frame::analyzeFilename $jobid $frame ]
           set name $temp(type)
        }
        if { [ info exists frms_tmp ] && [ string length $frms_tmp ] } {
           set frms $frms_tmp
        }
        ;## specific channels from explicitly named
        ;## frames.
        if { $frm_length && $que_length } {
           set interferometers {}
           set ifo_length 0
           set types           {}
           set typ_length 0
           if { [ regexp {\d{9,10}} $tims ] } {
              set times $tims
           } else {
              set gps $temp(gps)
              set times $gps-[ expr {$gps + $temp(tdt) - 1} ]
           }
           set frames $frms
           set channels $ques
        }
        ;## specific channels over some time range with
        ;## ifo(s) specified.  this is slightly complicated
        ;## by the fact that frames will have channels for
        ;## more than one ifo, as in H0, H1 and H2 channels in
        ;## a Hanford frame...
        if { $typ_length && $ifo_length && $tim_length && $que_length } {
           foreach ifo $ifos {
              set types $typs
              set interferometers $ifos
              set times $tims
              set frames {}
              set channels $ques
           }
        }
     } err ] } {
        return -code error "[ myName ]: $err"
     }
     return [ list $types $interferometers $times $frames $channels ]
}

§   §   §

Name: frame::expandDataQuery

Description:
Parses the framequery option from the frame commands and returns a list of two elements for each query item, the c++ command that should be called, and the indices to call it over if applicable.

Parameters: Usage:

Comments:

proc frame::expandDataQuery { query } {
     set errors [ list ]
     set item   [ list ]
     set query_pat {(\S+)\s*\(\s*([^\)]*)\s*\)}
     if { [ llength $query ] == 1 } {
        set query [ lindex $query 0 ]
     }
     ;## seed the query accessor method name
     foreach item $query {
        if { [ catch {
           set pathological 0
           set names  [ list ]
           ;## either it matches or it doesn't, no big deal
           regexp $query_pat $item -> item names
           regsub {\s+} $names "," names
           ;## should probably be a nocase match...
           ;## PR #2134 - nocase where appropriate 04/13/04
           switch -regexp -- $item {
                     {concat} { ;## this may not belong here
                           set op concat
                           }
           {([Aa]ll|[Ff]ull)} {
                           set op fullFrame2container
                           set names [ list ]
                           }
                {[Dd]etector$} {
                           set op DetectorProc
                           }
              {[Dd]etector[Pp]roc} {
                           set op DetectorProc
                           }
              {[Dd]etector[Ss]im} {
                           set op DetectorSim
                           }
       {([Aa][Dd][Cc]|[Ss][Ee][Rr]|[Ss][Ii][Mm]|[Pp][Rr][Oo][Cc])} {
                           regexp -nocase {Adc|Ser|Sim|Proc} $item op
                           set op [ ucase -strict $op ]
                           }
	       {([Ss]tat[Dd]ata|[Ss]ummary)} {
                           regexp -nocase {Stat|Summary} $item op
                           set op [ ucase -strict $op ]
                           }
                   default {
                           set pathological 1
                           set op {}
                           }
           }
           set names [ numRange $names ]
           ;## expand query accessor seed into command name
           ;## by appending atoms to op as long as they match
           ;## the query string
           while { 1 } {
              foreach atom $::frame::atoms {
                 if { [ regexp -nocase -- ^$op$atom $item ] } {
                    set op $op$atom
                    break
                 }
                 ;## don't leave a dangling atom if there was
                 ;## no match
                 set atom {}
              }
              ;## stop name building the first time nothing
              ;## matches
              if { ! [ string length $atom ] } { break }
           }
        } err ] } {
           ;## collect errors, and try to continue
           lappend errors $err
        }
        ;## attach "getFrame" prefix as appropriate
        if { ! [ regexp -nocase {((get|full)Frame|concat)}  $op ] } {
           set op getFrame$op
        }
        ;## interpose "Data" atom if we think it's lacking
        if { ! $pathological } {
           if { ! [ llength [ info commands $op ] ] } {
              regsub {getFrame[A-Z][a-z]+} $op {&Data} op
           }
        }
        ;## a couple of simple replacements
        regsub {SimDataEvent} $op SimEvent op
        regsub {SummaryData}  $op Summary  op
        ;## and test the resulting operation for existence
        if { ! [ llength [ info commands $op ] ] } {
           if { [ string equal getFrame $op ] } {
              lappend errors "badly formed query atom: '$item'"
           } else {
              lappend errors "calculated operation: '$op' does not exist"
           }
        } else {
           ;## and if it does, add it to the ops list
           lappend retval [ list $op $names ]
        }
     } ;## end of foreach
     if { [ llength $errors ] && [ info exists retval ] } {
        return -code error "[ myName ]: $errors"
     }
     if { [ info exists retval ] } {
        return $retval
     } else {
        return -code error "[ myName ]: $errors (malformed query?)"
     }
}

§   §   §

Name: frame::getOptArray

Description:

Parameters: Usage:

Comments:

proc frame::getOptArray { jobid } {
     set args     [ list ]
     set errs     [ list ]
     set opts     [ list ]
     if { [ catch {
        ;## automagically get all arguments which are
        ;## relevant to this API
        foreach [ list name rx default ] $::frame::options {
           set name [ string trim $name "=" ]
           set name [ string tolower $name ]
           if { [ info exists ::${jobid}($name) ] } {
              set arg [ set ::${jobid}($name) ]
              if { [ regexp -- $rx $arg ] } {
                 lappend args $name $arg
              } else {
                 lappend errs "$name '$arg'"
              }
           }
           lappend opts $name $default
        }
        if { [ llength $errs ] } {
           return -code error "malformed arguments: $errs"
        }
        foreach [ list opt val ] [ expandOpts ] {
           set opt [ string trim $opt - ]
           lappend arglist $opt $val
        }
     } err ] } {
        return -code error "[ myName ]: $err"
     }
     return $arglist
}

§   §   §

Name: frame::newJob

Description:
Insure that two jobs do not start on each other's heels, causing FILO behaviour.

Parameters: Usage:

Comments:

proc frame::newJob { { jobid "" } } {
     if { [ catch {
        set now [ clock seconds ]
        ;## get list of existing jobs
        set jobqueue $::frame::newjobqueue
        set ::frame::newjobqueue [ list ]
        ;## collect a new job if called by macro
        if { [ string length $jobid ] } {
           ;## uplevel #1 is the operator socket handler
           set cid [ set ::${jobid}(cid) ]
           ;## detach from the assistant manager
           if	{ [ regexp {8.4} $::tcl_version ] } {
            	foreach entry [ trace info variable ::$cid ] {
        			foreach { oplist cmd } $entry { break }
        			trace remove variable ::$cid $oplist $cmd
                }
			} else {
           		trace vdelete ::$cid w "reattach $jobid $cid"
           	}
           lappend jobqueue [ list $jobid $cid $now 0 ]
        }
        ;## make certain that at least 5 seconds have elapsed
        ;## since the last job was started
        set rate [ expr { $now - $::last_job_start_time } ]
        if { $rate < $::FRAME_NEW_JOB_RATE } {
           set ::frame::newjobqueue $jobqueue
           return {}
        }
        set triggered 0
        foreach item $jobqueue {
           foreach [ list jobid cid stime metric ] $item { break }
           regexp {\d+} $jobid job
           set jobid $::RUNCODE$job
           if { $triggered } {
              ;## the job goes back on the list...
              lappend ::frame::newjobqueue \
                 [ list $jobid $cid $stime $metric ]
           } elseif { [ info exists ::filecache$job ] } {
              set test [ set ::filecache$job ]
              if { ! [ string length $test ] } {
                 set err "aborted job $jobid"
                 frame::newJobAbort $jobid $cid $err
                 continue
              }
              if { [ llength $test ] == 1 && \
                   [ llength [ lindex [ join $test ] 0 ] ] == 3 } {
                 set ::filecache$job [ join [ set ::filecache$job ] ]
              }
              if { [ info exists ::DEBUG_FILECACHE ] && \
                   [ string equal 1 $::DEBUG_FILECACHE ] } {
                 addLogEntry [ set ::filecache$job ] purple
              }
              ;## okay, we're ready to go!!
              set triggered 1
              set ::last_job_start_time $now
              after 0 frame::newJobCallback $jobid $cid
           } else {
              if { ($now - $stime) > $::DELAY_RECV_FRAMES_FROM_DISKCACHE_SECS } {
                 set err "no frames received from diskcache API"
                 frame::newJobAbort $jobid $cid $err
              } else {
                 ;## the job goes back on the list...
                 lappend ::frame::newjobqueue \
                    [ list $jobid $cid $stime $metric ]
              }
           }
        }
      } err ] } {
        if { [ string length $err ] } {
           if { ! [ info exists cid ] } {
              set cid unknown_sockid
           }
           frame::newJobAbort $jobid $cid $err
         }
      }
}

§   §   §

Name: frame::newJobAbort

Description:

Parameters: Usage:

Comments:

proc frame::newJobAbort { jobid cid err } {
     if { [ catch {
        set flag 0
        regexp {\d+} $jobid job
        set jobid $::RUNCODE$job
        addLogEntry $err red
        #set fd [ open ${::DUMPDIR}/${jobid}.params w ]
        #puts $fd [ array get ::$jobid ]
        #close $fd
        set ::$cid [ list 3 $err error! ]
        ::reattach $jobid $cid
        if { [ info exists ::${jobid}(Frame) ] } {
           ::unset ::${jobid}(Frame)
        }
        set flag 1
        frame::killJob $jobid
     } err ] } {
        addLogEntry $err red
        if { $flag == 0 } {
           frame::killJob $jobid
        }
     }
}

§   §   §

Name: frame::newJobCallback

Description:

Parameters: Usage:

Comments:

proc frame::newJobCallback { jobid cid } {
     set outfiles [ list ]
     if { [ catch {
        regexp {\d+} $jobid job
        set jobid $::RUNCODE$job
        array set opt [ frame::getOptArray $jobid ]
        set rp     $opt(returnprotocol)
        set of     $opt(outputformat)
        set concat $opt(concatenate)
        set targ   $opt(frametarget)
        set ag     $opt(allowgaps)
        ;## Peter Shawhan asked for this (PR#823)
        regsub -- {(http|ftp|file|port):([^\/])} $rp {\2} rp
        ;## are results going to another API?
        set target $opt(frametarget)
        if { [ string length $target ] } {
           set api_rx ^([ join $::API_LIST | ])\$
           ;## determine whether the target is an api or a file
           if { [ regexp -- $api_rx $target ] } {
              set rp $target
           }
        }
        ;## new style complex framequery handler with
        ;## full backwards compatibility
        set _ifos   [ list ]
        set _times  [ list ]
        set _frames [ list ]
        set _dq     [ list ]
        set parsed_frame_query \
           [ frame::parseFrameQuery $jobid $opt(framequery) ]
        ;## how many framequeries were chained together?
        set N_queries [ llength $parsed_frame_query ]
        foreach retval $parsed_frame_query {
           foreach [ list _types _ifos _times _frames _dq ] $retval {
              ;## old style dataqueries will return a short list
              if { ! [ string length $_dq ] } {
                 set dq $opt(framequery)
              } elseif { ! [ string length $_types ] } {
                 set types R
                 set ifos   $_ifos
                 set times  $_times
                 set frames $_frames
                 set dq     $_dq
              } else {
                 set types  $_types
                 set ifos   $_ifos
                 set times  $_times
                 set frames $_frames
                 set dq     $_dq
              }
              foreach item { types ifos times frames dq } {
                 if { [ llength [ set $item ] ] == 1 } {
                    set $item [ lindex [ set $item ] 0 ]
                 }
              }
           }
           set rawdq $dq
           set dq [ frame::expandDataQuery $dq ]
           ;## some fancy flow control here so we can, if
           ;## necessary, synch up framequeries.
           if { $concat != -1 && $N_queries > 1 } {
              eval lappend dqs $dq
              incr N_queries -1
              eval lappend rawdqs $rawdq
              continue
           }
           if { [ info exists dqs ] } {
              eval lappend dqs $dq
              eval lappend rawdqs $rawdq
              set dq $dqs
              set rawdq $rawdqs
              unset rawdqs
              unset dqs
           }
           ;## default interferometer is '0'
           if { ! [ llength $ifos ] } { set ifos 0 }
           if { $::DEBUG_QUERY_EXPANSION } {
              set debug "types: $types ifos: '$ifos'  frames: '$frames' "
              append debug " times: '$times'  query: '$dq' "
              append debug " of: '$of'  rp: '$rp'"
              addLogEntry $debug purple
           }
           foreach ifo $ifos {
              foreach type $types {
                 lappend contptrs [ frame::runNewJob \
                    $jobid $concat $ifo $frames $times $dq $type \
                    $of $rp $ag $rawdq ]
              }
           }
        } ;## end of foreach on -framequery option
        ;## we only handle output up here if we are going through
        ;## collectElements, which uses a special -concatenate
        ;## option value of '-1'.
        if { $concat == -1 } {
           unset ::filecache$job
           set contptrs [ frame::mergePointers $jobid $contptrs ]
           set contptrs [ list $jobid $contptrs ]
           set outfiles [ frame::output $jobid $targ $contptrs $of ]
           frame::destructImage $jobid -nocomplain
           frame::managePointers $jobid destroy .+
           frame::reattachMsg $jobid $cid $outfiles
           reattach $jobid $cid
           if { [ info exists ::$jobid ] } {
              ::unset ::$jobid
           }
        }
     } err ] } {
        frame::newJobAbort $jobid $cid $err
     }
}

§   §   §

Name: frame::reattachMsg

Description:

Parameters: Usage:

Comments:

proc frame::reattachMsg { jobid cid filenames } {
     if { [ catch {
        set of      [ set ::${jobid}(-outputformat)   ]
        set rp      [ set ::${jobid}(-returnprotocol) ]
        set target  [ set ::${jobid}(-targetapi)      ]
        set subj    [ set ::${jobid}(-subject)        ]
        set query   [ set ::${jobid}(-framequery)     ]
        set ftarget [ set ::${jobid}(-frametarget)    ]
        ;## default return value is a continuation
        if { [ regexp {LIGO_LW} $of ] || \
             [ string length $target ] } {
           set msg [ list  0 0 0 ]
        }
        if { ! [ string length $subj ] } {
           set subj "frame API output"
        }
        set protocol http
        ;## if the frame API is the target, and the user
        ;## supplied an explicit return protocol, use it.
        ;## otherwise use http.
        if { [ string length $rp ] &&
             [ regexp -nocase {frame} $target ] } {
           regexp -nocase {^(file|ftp|http)} $rp protocol
           set msg [ macroReturnMsg $jobid $protocol $filenames ]
           set msg [ list 2 [ lindex $msg 1 ] $subj ]
        ;## no specified return protocol but frame is target
        } elseif { [ regexp -nocase {frame} $target ] } {
           set jobdir [ jobDirectory ]
           regsub $::HTTPDIR $jobdir {} jobdir
           set msg    "Your results:\n${filenames}\ncan be found at:\n"
           append msg "$::HTTPURL$jobdir/"
           set msg [ list 2 $msg $subj ]
        ;## another API is the target, but we dropped files
        ;## somehow.
        } elseif { ! [ info exists msg ] } {
           regexp -nocase {^(file|ftp|http)} $rp protocol
           set msg [ macroReturnMsg $jobid $protocol $filenames ]
           set msg [ list 2 [ lindex $msg 1 ] $subj ]
        }
        set ::$cid $msg
     } err ] } {
        return -code error "[ myName ]: $err"
     }
}

§   §   §

Name: frame::runNewJob

Description:

Parameters: Usage:

Comments:

proc frame::runNewJob { jobid concat ifo frames times query type of rp ag rawdq } {
     if { [ catch {
        set contptrs [ list ]
        set dir [ jobDirectory ]
        set target [ set ::${jobid}(-frametarget) ]
        if { [ info exists ::${jobid}(-autoexpand) ] } {
           set expand 1
        } else {
           set expand 0
        }
        ;## if we are going to concatenate, we need to know
        ;## if we're making frames or just packing up channels
        if { [ regexp -nocase {frame} $of ] && $concat == 1 } {
           set fo 1
        } else {
           set fo 0
        }
        if { $concat == 1 && $fo == 0 } {
           frame::concatThreadProto \
              $jobid $ifo $times $type $frames $of $rp $rawdq $ag
        } elseif { $concat == -1 } {
           set contptrs \
              [ frame::collectElements $jobid $frames $times $query ]
        } else {
           frame::createFramesProto  \
              $jobid $ifo $times $type $frames \
              $of $rp $rawdq $ag $concat $expand
        }
     } err ] } {
        return -code error "[ myName ]: $err"
     }
     return $contptrs
}

§   §   §

Name: frame::mergePointers

Description:
Merge return pointers into a single container on a per job basis.

Parameters: Usage:

Comments:

Pointers as received here are in fact jobid/ptr pairs
proc frame::mergePointers { jobid contptrs } {
     if { [ catch {
        set seqpt {}
        set ptr   {}
        set newcontp [ lindex $contptrs 0 ]
        set newcontp [ lindex $newcontp 1 ]
        if { [ llength $contptrs ] > 1 } {
           set newcontp [ ilwd::newcontp ]
           frame::managePointers $jobid add $newcontp
           foreach pair $contptrs {
              foreach [ list jobid ptr ] $pair {
                 set seqpt "addContainerElement($newcontp $ptr):"
                 ::addContainerElement $newcontp $ptr
                 set seqpt {}
                 frame::managePointers $jobid delete $ptr
              }
           }
        }
     } err ] } {
        return -code error "[ myName ]:$seqpt: $err"
     }
     return $newcontp
}

§   §   §

Name: frame::analyzeFilename

Description:
Encapsulates all the file naming conventions ever proposed, with heaviest weight to the "current" proposal.
First fallback is the so-called Thursday format, which Hanford started using for some time on 11-03-2001.

Parameters: Usage:

Comments:

proc frame::analyzeFilename { jobid filename } {
     if { [ catch {
        set atoms [ frame::analyzeOfficialFilename $jobid $filename ]
        if { [ llength $atoms ] == 6 } {
           set frame(type) official
           set frame(ifo) [ lindex $atoms 1 ]
           set frame(typ) [ lindex $atoms 2 ]
           set frame(gps) [ lindex $atoms 3 ]
           ;## we will have to use the tdt and
           ;## getFrameNumber to set the "N" and "dt" later
           set frame(frn) {}
           set frame(tdt) [ lindex $atoms 4 ]
           set frame(ext) gwf
           set atoms [ array get frame ]
        } else {
           set msg "frame filename formats not adhereing to "
           append msg "the 'official' naming convention are "
           append msg "no longer supported, and the frame: "
           append msg "'$filename' does not conform to the "
           append msg "ifo-frame_type-gps-dt.gwf format."
           return -code error $msg
        }
     } err ] } {
        return -code error "[ myName ]: $err"
     }
     return $atoms
}

§   §   §

Name: frame::analyzeOfficialFilename

Description:
Analyze filenames adhereing to the current proposed frame file naming specification, which looks like:
S-G-T-D.gwf where "S" is the interferometer (Site), "G" is the GPS time stamp, "T" is the dt of the file, and "D" is what used to be the extension.
"N" can now only be conned at access time by using the getFrameNumber on an open frame file pointer.

Parameters: Usage:

Comments:

Fallback support for 'Thursday' frames added per Peter during meeting on 11-05-2001.
proc frame::analyzeOfficialFilename { jobid filename } {
     if { [ catch {
        set atoms [ list ]
        ;## this regexp will return 6 elements when it
        ;## matches:
        ;## given:
        ;##          G-R-6666666666-1.gwf
        ;## returns:
        ;##          G-R-6666666666-1.gwf G R 6666666666 1 .gwf
        ;##
        set rx {^([^-]+)-([^-]+)-(\d{1,10})-(\d+)(\.gwf)$}
        set tail [ file tail [ string trim $filename ] ]
        set tail [ string trim $tail ]
        set atoms [ regexp -inline -- $rx $tail ]
        ;## if the pattern matched, but the filename was
        ;## somehow malformed... should not be possible
        if { [ llength $atoms ] } {
           if { [ regsub -all -- {-} $tail {} foo ] != 3 } {
              set msg "malformed 'official' frame filename: '$tail' "
              append msg "has extra '-' character(s), so does not "
              append msg "conform to gps-frame_type-gps-dt.gwf format."
              return -code error $msg
           }
        }
     } err ] } {
        return -code error "[ myName ]: $err"
     }
     return $atoms
}

§   §   §

Name: frame::getNumber

Description:
Given a filename, open it and use getFrameNumber to find out how many frames it has. Clean up!

Parameters: Usage:

Comments:

proc frame::getNumber { jobid filename } {
     if { [ catch {
        set seqpt "openFrameFileThread($filename r):"
        set ffp [ openFrameFileThread $jobid $filename r ]
        set seqpt "getFrameNumber($ffp):"
        set frn [ getFrameNumber $ffp ]
        set seqpt "closeFrameFile($ffp):"
        closeFrameFile $ffp
        set ffp [ list ]
        if { ! $frn } {
           set msg "getFrameNumber reported '0' frames in '$filename'"
           return -code error $msg
        }
     } err ] } {
        if { [ info exists ffp ] } {
           if { [ string length $ffp ] } {
              catch { closeFrameFile $ffp }
           }
        }
        return -code error "[ myName ]:$seqpt $err"
     }
     return $frn
}

§   §   §

Name: frame::rationalizedFilenameData

Description:
Get important values from the frame name for frame::insertElements and frame::collectElements
Parameters: Usage:

Comments:

proc frame::rationalizedFilenameData { jobid filename } {
     if { [ catch {
        array set temp [ frame::analyzeFilename $jobid $filename ]
        set gps $temp(gps)
        set dt  $temp(tdt)
        set N   $temp(frn)
        if { !  [ string length $N  ] } { set N  1 }
        if { !  [ string length $dt ] } { set dt 1 }
        set tdt [ expr { $N * $dt } ]
        ;## if we have an unparsible frame name, go for broke!!
        if { [ string equal $temp(type) unknown ] } {
           set gps 0
           set dt 1
           set N 1
           set tdt 1
        }
        set type $temp(typ)
     } err ] } {
        return -code error "[ myName ]: $err"
     }
     return [ list $type $gps $N $dt $tdt ]
}

§   §   §

Name: frame::collectElements

Description:

Parameters: Usage:

Comments:

proc frame::collectElements { jobid frames times query } {
     if { [ catch {
        set ptrs [ list ]
        set errs [ list ]
        regexp {\d+} $jobid job
        set jobid $::RUNCODE$job
        foreach [ list times frames ] \
           [ frame::fixTimesAndFrames $times $frames ] { break }
        array set block \
           [ frame::matchTimesAndFrames $jobid $times $frames ]
        set contp [ ilwd::newcontp ]
        frame::managePointers $jobid add $contp
        setElementAttribute $contp comment "$jobid $frames $query"
        foreach frame $frames {
           if { ! [ string length $frame ] } { continue }
           set ptrs [ list ]
           set times $block($frame)
           foreach [ list type gps N fdt tdt ] \
              [ frame::rationalizedFilenameData $jobid $frame ] { break }
           if { ! $gps } { set gps [ lindex $times 0 ] }
           set framep [ frame::file2ptr $jobid $frame ]
           foreach time $times {
              set id [ file tail $frame ]
              foreach item $query {
                 foreach [ list method chans ] $item { break }
                 if { ! [ llength $chans ] } { set chans 0 }
                 if { [ regexp {(\d+)-(\d+)} $time -> start end ] } {
                    set deltat [ expr { $end - $start } ]
                 } else {
                    set start [ lindex $time 0 ]
                    set deltat 1
                 }
                 if { $start > $gps && $start < ($gps + $fdt) } {
                    set offset [ expr { $start - $gps } ]
                 } else {
                    set offset 0
                 }
                 set exp !${offset}!${deltat}!
                 if { [ catch {
                    foreach chan $chans {
                       ;## is it a resample request?
                       foreach [ list chan resample ] \
                          [ frame::resampleParser $chan ] { break }
                       if { ! [ regexp {!} $chan ] } {
                          set chan $chan$exp
                       }
                       set ptr \
                          [ frame::method2ptr $jobid $item $chan $id 1 $resample ]
                       ilwd::addElement $ptr $contp
                       frame::destructElementWrap $ptr
                    }
                 } err ] } {
                    lappend errs "$id: $err"
                 }
              }
           } ;## end of foreach on time
           update
        }
        if { [ llength $errs ] } {
           return -code error $errs
        }
     } err ] } {
        return -code error "[ myName ]: $err"
     }
     return [ list $jobid $contp ]
}

§   §   §

Name: frame::fixTimesAndFrames

Description:
Fix various formats of times and frames while things are still in flux. I hope this gets cleaner soon.

Parameters: Usage:

Comments:

proc frame::fixTimesAndFrames { times frames } {
     if { [ catch {
        set jobid [ uplevel set jobid ]
        regexp {\d+} $jobid job
        set jobid $::RUNCODE$job
        set intimes $times
        set inframes $frames
        if { ! [ string length [ lindex $frames 0 ] ] } {
           set frames [ set ::filecache$job ]
           if { [ llength $frames ] == 1 } {
              set frames [ lindex $frames 0 ]
           }
           ;## cache::concatGetFilenames could return a list
           ;## of three elements, times frames and gapflag...
           ;## maybe can't happen here.
           if { [ regexp {^\d{9,10}-\d{9,10}$} [ lindex $frames 0 ] ] } {
              set times  [ lindex $frames 0 ]
              set frames [ lindex $frames 1 ]
           }
        }
     if { ! [ string length $times ] } { set times 0 }
     if { [ llength $times ] == 1 } {
        set times [ lindex $times 0 ]
     }
     if { [ llength $frames ] == 1 } {
        set frames [ lindex $frames 0 ]
     }
     if { $::DEBUG_QUERY_EXPANSION } {
        set msg "times_in: '$intimes' frames_in: '$inframes' "
        append msg "times_out: '$times' frames_out: '$frames'"
        addLogEntry $msg purple
     }
     } err ] } {
        return -code error "[ myName ]: $err"
     }
     return [ list $times $frames ]
}

§   §   §

Name: frame::matchTimesAndFrames

Description:
Associate a list of times with each frame. Try to collapse times to ranges per frame as possible.

Parameters: Usage:

Comments:

proc frame::matchTimesAndFrames { jobid times frames } {
     if { [ catch {
        set ranges [ list ]
        set intimes $times
        set times [ numRange $times ]
        set inframes $frames
        foreach frame $frames {
           array set temp [ frame::analyzeFilename $jobid $frame ]
           set N $temp(frn)
           if { ! [ string length $N  ] } { set N  1 }
           set dt $temp(tdt)
           if { ! [ string length $dt ] } { set dt 1 }
           set start $temp(gps)
           set dt  [ expr { $N * $dt } ]
           set end [ expr $start + $dt ]
           set timedata($frame) [ list ]
           if { [ string equal 0 $times ] } {
              set times $start
           }
           ;## we CAN prune the time list because a single
           ;## grouping of frames will be uniquely matched.
           ;## other matching frames will be grouped seperately.
           set i 0
           set tmp [ list ]
           foreach time $times {
               if { $time >= $start && $time < $end } {
                  lappend timedata($frame) $time
               } elseif { $time < $start } {
                  lappend tmp $time
               } elseif { $time >= $end } {
                  set tmp [ concat $tmp [ lrange $times $i end ] ]
                  break
               }
               incr i
           }
           set times $tmp
           ;## collapse timedata into ranges
           if { [ llength $timedata($frame) ] } {
              set t0 [ lindex $timedata($frame) 0 ]
              set tend $t0
              set ranges [ list ]
              foreach time [ lrange $timedata($frame) 1 end ] {
                 if { $time == $tend + 1 } {
                    set tend $time
                 } else {
                    if { $t0 == $tend } {
                       lappend ranges $t0
                    } else {
                       lappend ranges ${t0}-$tend
                    }
                    set t0 $time
                    set tend $t0
                 }
              }
              if { $t0 == $tend } {
                 lappend ranges $t0
              } else {
                 lappend ranges ${t0}-$tend
              }
              set timedata($frame) $ranges
           } ;## end of collapse of time data
           set last [ lindex $ranges end ]
           if { [ regexp {(\d+)-(\d+)} $last -> start end ] } {
              set last ${start}-[ expr { $end + 1 } ]
           } elseif { [ llength $timedata($frame) ] == 1 } {
              set last $timedata($frame)
           } else {
              set last ${end}-[ expr { $end + 1 } ]
           }
           set timedata($frame) [ lreplace $ranges end end $last ]
        } ;## end of foreach on frames
        if { $::DEBUG_QUERY_EXPANSION } {
           set msg "times_in: '$intimes' frames_in: '$inframes' "
           foreach frame [ array names timedata ] {
              append msg "times in '$frame': '$timedata($frame)'"
           }
           addLogEntry $msg purple
        }
     } err ] } {
        return -code error "[ myName ]: $err"
     }
     return [ array get timedata ]
}

§   §   §

Name: frame::timeWindow

Description:
Return a new channelp containing time-sliced OR frequency sliced data.

Parameters: Usage:

Comments:

Works with Adc and Proc data so far.
proc frame::timeWindow { jobid ptr { offset 0 } { dt 1 } } {
     if { [ catch {
        set seqpt {}
        if { $offset == 0 && $dt == 0 } {
           set err "offset and dt both '0'! "
           append err "a channel with a dt of 0 would result."
           return -code error $err
        }
        if { [ regexp {(Adc|Proc)} $ptr type ] } {
           set seqpt "getFrame${type}DataSlice($ptr $offset $dt):"
           set outptr [ getFrame${type}DataSlice $ptr $offset $dt ]
           frame::managePointers $jobid add $outptr
           frame::managePointers $jobid destroy $ptr
        } else {
           set outptr $ptr
        }
     } err ] } {
        return -code error "[ myName ]:$seqpt $err"
     }
     return $outptr
}

§   §   §

Name: frame::inFrame

Description:
Very slow function for accessing frames in frame
Parameters: Usage:

Comments:

Daq functions do not work on multi frame frames.
Calls readFrame repeatedly to position the wanted frame at the top of the framep. Then iterate through the read frames...
Concatenation will require the use of the old concat functions.
proc frame::inFrame { jobid filename { offset 0 } { delta 1 } } {
     if { [ catch {
        array set finfo [ frame::analyzeFilename $jobid $filename ]
        set seqpt "openFrameFileThread($filename r):"
        set fp [ openFrameFileThread $jobid $filename r ]
        set seqpt {}
        frame::managePointers $jobid add $fp
        ;## if the file has more than one frame
        if { $finfo(frn) > 1 } {
           ;## set the max frame offset to n
           set n $finfo(frn)
        } else {
           ;## otherwise set it to 1
           set n 1
        }
        set seqpt "getFrameNumber($filename $fp):"
        set N [ getFrameNumber $fp ]
        if { $N != $n } {
           return -code error "$filename has '$N' frames, not '$n'"
        }
        set seqpt {}
        ;## calculate time offset to nearest second (round up)
        set delta [ expr { double($delta) / $finfo(tdt) } ]
        set delta [ expr { int(ceil($delta)) } ]
        set framen [ expr { $offset + $delta } ]
        if { $framen > $N } {
           return -code error "offset > N for $filename: $framen > $N"
        }
        set i 0
        while { [ incr i ] <= $framen } {
           set seqpt "readFrame($fp):"
           set framep [ readFrame $fp ]
        }
        set seqpt {}
        frame::managePointers $jobid add $framep
        frame::setImage $jobid $filename $framep
     } err ] } {
        frame::managePointers $jobid destroy .+
        return -code error "[ myName ]:$seqpt $err"
     }
     return $framep
}

§   §   §

Name: frame::method2ptr

Description:
Contains all the switching code for accessing arbitrary frame objects. Special cases are handled as generally as possible. Returns an ilwd container pointer.

Parameters: Usage:

Comments:

This is the "guts" of frame::collectElements
proc frame::method2ptr { jobid item index frame { ilwd 1 } { resample 0 } } {
     set seqpt {}
     set ptr [ list ]
     ;## index 0 has inner accessor, index 1 has outer accessor.
     ;## if 0 and 1 are identical there is no inner accessor call.
     set class_rx {(getFrame[A-Z][a-z]+(?:Data|Event)?)(\S*)}
     ;## identifies an ilwd pointer as opposed to a framecpp ptr
     set ilwd_rx {^_[0-9a-f]+_p_Ldas(Element|Container|ArrayBase)$}
     ;## a stat structure
     set stat_rx {(getFrameStatData)([a-zA-Z]+)?}
     set frame_rx {^getFrame(Adc|Proc|Sim|Ser|Stat)Data$}
     if { [ catch {
        if { [ file exists $frame ] } {
           set framep [ frame::open_r $jobid $frame ]
        } else {
           foreach [ list frame framep ] [ frame::getImage $jobid ] {
              break
           }
        }
        if { [ info exists ::DEBUG_METHOD2PTR ] && \
             [ string equal 1 $::DEBUG_METHOD2PTR ] } {
           set msg "frame: $frame channel: $index method: $item"
           addLogEntry $msg purple
        }
        foreach [ list type gps N fdt tdt ] \
           [ frame::rationalizedFilenameData $jobid $frame ] { break }
        foreach [ list idx off dt ] \
           [ frame::parseChannelSlice $index ] { break }
        ;## class_rx matches all methods.  inner accessor
        ;## points to structure, outer accessor points to
        ;## element of structure IF different from inner.
        set method [ lindex $item 0 ]
        set inner $method
        set outer $method
        regexp $class_rx $method inner outer
        ;## are we slicing?
        if { $dt != $fdt && [ regexp {(Adc|Proc)Data$} $inner ] } {
           set ptr [ frame::timeSliceChannel \
              $jobid $frame [ list $outer $index ] $off $dt ]
           ;## at this point the offset and dt need to be set to 0
           ;## if the Proc structure did NOT contain time series data.
           ;## and we know that because samplerate will be '-1'.
           if { [ regexp {Proc} $ptr ] } {
              set seqpt "getFrameProcDataType($ptr):"
              set samplerate [ getFrameProcDataType