tie.tcl

Go to the documentation of this file.
00001 /*  tie.tcl --*/
00002 /* */
00003 /*  Tie arrays to persistence engines.*/
00004 /* */
00005 /*  Copyright (c) 2004 Andreas Kupries <andreas_kupries@users.sourceforge.net>*/
00006 /* */
00007 /*  See the file "license.terms" for information on usage and redistribution*/
00008 /*  of this file, and for a DISCLAIMER OF ALL WARRANTIES.*/
00009 /*  */
00010 /*  RCS: @(#) $Id: tie.tcl,v 1.7 2006/09/19 23:36:18 andreas_kupries Exp $*/
00011 
00012 /*  ### ### ### ######### ######### #########*/
00013 /*  Requisites*/
00014 
00015 package require snit
00016 package require cmdline
00017 
00018 /*  ### ### ### ######### ######### #########*/
00019 /*  Implementation*/
00020 
00021 /*  ### ### ### ######### ######### #########*/
00022 /*  Public API*/
00023 
00024 namespace ::tie {}
00025 
00026 ret  ::tie::tie (type avar , type args) {
00027     # Syntax : avar ?-open? ?-save? ?-merge? dstype dsargs...?
00028 
00029     variable registry
00030 
00031     upvar 1 $avar thearray
00032 
00033     if {![array exists thearray]} {
00034     return -code error "can't tie to \"$avar\": no such array variable"
00035     }
00036 
00037     # Create shortcuts for the options, and initialize them.
00038     foreach k {open save merge} {upvar 0 opts($k) $k}
00039     set open  0
00040     set save  0
00041     set merge 0
00042 
00043     # Option processing ...
00044 
00045     array set opts [GetOptions args]
00046 
00047     # Basic validation ...
00048 
00049     if {$open && $save} {
00050     return -code error "-open and -save exclude each other"
00051     } elseif {!$open && !$save} {
00052     set open 1
00053     }
00054 
00055     if {![llength $args]} {
00056     return -code error "dstype and type arguments missing"
00057     }
00058     set type [lindex $args 0]
00059     set args [lrange $args 1 end]
00060 
00061     # Create DS object from type (DS class) and args.
00062     if {[::info exists registry($type)]} {
00063     set type $registry($type)
00064     }
00065     set dso [eval [concat $type %AUTO% $args]]
00066 
00067     Connect thearray $open $merge $dso
00068     return [NewToken thearray $dso]
00069 }
00070 
00071 ret  ::tie::untie (type avar , type args) {
00072     # Syntax : arrayvarname ?token?
00073 
00074     variable mgr
00075     variable tie
00076 
00077     upvar 1 $avar thearray
00078 
00079     switch -exact -- [llength $args] {
00080     0 {
00081         # Remove all ties for the variable. Do nothing if there
00082         # are no ties in place.
00083 
00084         set mid [TraceManager thearray]
00085         if {$mid eq ""} return
00086     }
00087     1 {
00088         # Remove a specific tie.
00089 
00090         set tid [lindex $args 0]
00091         if {![::info exists tie($tid)]} {
00092         return -code error "Unknown tie \"$tid\""
00093         }
00094 
00095         foreach {mid dso} $tie($tid) break
00096         set midvar [TraceManager thearray]
00097 
00098         if {$mid ne $midvar} {
00099         return -code error "Tie \"$tid\" not associated with variable \"$avar\""
00100         }
00101 
00102         set pos       [lsearch -exact $mgr($mid) $tid]
00103         set mgr($mid) [lreplace $mgr($mid) $pos $pos]
00104 
00105         unset tie($tid)
00106         $dso destroy
00107 
00108         # Leave the manager in place if there still ties
00109         # associated with the variable.
00110         if {[llength $mgr($mid)]} return
00111     }
00112     default {
00113         return -code error "wrong#args: array ?token?"      
00114     }
00115     }
00116 
00117     # Delegate full removal to common code.
00118     Untie $mid thearray
00119     return
00120 }
00121 
00122 ret  ::tie::info (type cmd , type args) {
00123     variable mgr
00124     if {$cmd eq "ties"} {
00125     if {[llength $args] != 1} {
00126         return -code error "wrong#args: should be \"tie::info ties avar\""
00127     }
00128     upvar 1 [lindex $args 0] thearray
00129     set mid [TraceManager thearray]
00130     if {$mid eq ""} {return {}}
00131 
00132     return $mgr($mid)
00133     } elseif {$cmd eq "types"} {
00134     if {[llength $args] != 0} {
00135         return -code error "wrong#args: should be \"tie::info types\""
00136     }
00137     variable registry
00138     return [array get registry]
00139     } elseif {$cmd eq "type"} {
00140     if {[llength $args] != 1} {
00141         return -code error "wrong#args: should be \"tie::info type dstype\""
00142     }
00143     variable registry
00144     set type [lindex $args 0]
00145     if {![::info exists registry($type)]} {
00146         return -code error "Unknown type \"$type\""
00147     }
00148     return $registry($type)
00149     } else {
00150     return -code error "Unknown command \"$cmd\", should be ties, type, or types"
00151     }
00152 }
00153 
00154 ret  ::tie::register (type dsclasscmd _, type as_ , type dstype) {
00155     variable registry
00156     if {$_as_ ne "as"} {
00157     return -code error "wrong#args: should be \"tie::register command 'as' type\""
00158     }
00159 
00160     # Resolve a chain of type definitions right now.
00161     while {[::info exists registry($dsclasscmd)]} {
00162     set dsclasscmd $registry($dsclasscmd)
00163     }
00164 
00165     set registry($dstype) $dsclasscmd
00166     return
00167 }
00168 
00169 /*  ### ### ### ######### ######### #########*/
00170 /*  Internal : Framework state*/
00171 
00172 namespace ::tie {
00173     /*  Registry of short names and their associated class commands*/
00174 
00175     variable  registry
00176     array  registry =  {}
00177 
00178     /*  Management databases for the ties.*/
00179     /* */
00180     /*     mgr   : mgr id  -> list (tie id)*/
00181     /*     tie   : tie id  -> (mgr id, dso cmd)*/
00182     /* */
00183     /*     array  ==> mgr -1---n-> tie*/
00184     /*                 ^           |*/
00185     /*                 +-1-------n-+*/
00186     /* */
00187     /*     lock  : mgr id x key -> 1/exists 0/!exists*/
00188 
00189     /*  Database of managers for arrays.*/
00190     /*  Also counter for the generation of mgr ids.*/
00191 
00192     variable mgrcount 0
00193     variable mgr ; array  mgr =  {}
00194 
00195 
00196     /*  Database of ties (and their tokens).*/
00197     /*  Also counter for the generation of tie ids.*/
00198 
00199     variable  tiecount 0
00200     variable  tie ; array  tie =  {}
00201 
00202     /*  Database of locked arrays, keys, and data sources.*/
00203 
00204     variable  lock ; array  lock =  {}
00205 
00206     /*  Key | Meaning*/
00207     /*  --- + -------*/
00208     /*  $mid,$idx   | Propagation for index $idx is in progress.*/
00209 }
00210 
00211 /*  ### ### ### ######### ######### #########*/
00212 /*  Internal : Option processor*/
00213 
00214 ret  ::tie::GetOptions (type arglistVar) {
00215     upvar 1 $arglistVar argv
00216 
00217     set opts [lrange [::cmdline::GetOptionDefaults {
00218     {open        {}}
00219     {save        {}}
00220     {merge       {}}
00221     } result] 2 end] ;# Remove ? and help.
00222 
00223     set argc [llength $argv]
00224     while {[set err [::cmdline::getopt argv $opts opt arg]]} {
00225     if {$err < 0} {
00226         set olist ""
00227         foreach o [lsort $opts] {
00228         if {[string match *.arg $o]} {
00229             set o [string range $o 0 end-4]
00230         }
00231         lappend olist -$o
00232         }
00233         return -code error "bad option \"$opt\",\
00234             should be one of\
00235             [linsert [join $olist ", "] end-1 or]"
00236     }
00237     set result($opt) $arg
00238     }
00239     return [array get result]
00240 }
00241 
00242 /*  ### ### ### ######### ######### #########*/
00243 /*  Internal : Token generator*/
00244 
00245 ret  ::tie::NewToken (type avar , type dso) {
00246     variable tiecount
00247     variable tie
00248     variable mgr
00249 
00250     upvar 1 $avar thearray
00251 
00252     set     mid         [NewTraceManager thearray]
00253     set     tid         tie[incr tiecount]
00254     set     tie($tid)   [list $mid $dso]
00255     lappend mgr($mid)   $tid
00256     return $tid
00257 }
00258 
00259 /*  ### ### ### ######### ######### #########*/
00260 /*  Internal : Trace Management*/
00261 
00262 ret  ::tie::TraceManager (type avar) {
00263     upvar 1 $avar thearray
00264 
00265     set traces [trace info variable thearray]
00266 
00267     foreach t $traces {
00268     foreach {op cmd} $t break
00269     if {
00270         ([llength $cmd] == 2) &&
00271         ([lindex $cmd 0] eq "::tie::Trace")
00272     } {
00273         # Our internal manager id is the first argument of the
00274         # trace command we attached to the array.
00275         return [lindex $cmd 1]
00276     }
00277     }
00278     # No framework trace was found, there is no manager.
00279     return {}
00280 }
00281 
00282 ret  ::tie::NewTraceManager (type avar) {
00283     variable mgrcount
00284     variable mgr
00285 
00286     upvar 1 $avar thearray
00287 
00288     set mid [TraceManager thearray]
00289     if {$mid ne ""} {return $mid}
00290 
00291     # No manager was found, we have to create a new one for the
00292     # variable.
00293 
00294     set mid [incr mgrcount]
00295     set mgr($mid) [list]
00296 
00297     trace add variable thearray \
00298         {write unset} \
00299         [list ::tie::Trace $mid]
00300 
00301     return $mid
00302 }
00303 
00304 ret  ::tie::Trace (type mid , type avar , type idx , type op) {
00305     #puts "[pid] Trace $mid $avar ($idx) $op"
00306 
00307     variable mgr
00308     variable tie
00309     variable lock
00310 
00311     upvar $avar thearray
00312 
00313     if {($op eq "unset") && ($idx eq "")} {
00314     # The variable as a whole is unset. This
00315     # destroys all the ties placed on it.
00316     # Note: The traces are already gone!
00317 
00318     Untie $mid thearray
00319     return
00320     }
00321 
00322     if {[::info exists lock($mid,$idx)]} {
00323     #puts "%% locked $mid,$idx"
00324     return
00325     }
00326     set lock($mid,$idx) .
00327     #puts "%% lock $mid,$idx"
00328 
00329     if {$op eq "unset"} {
00330     foreach tid $mgr($mid) {
00331         set dso [lindex $tie($tid) 1]
00332         $dso unsetv $idx
00333     }
00334     } elseif {$op eq "write"} {
00335     set value $thearray($idx)
00336     foreach tid $mgr($mid) {
00337         set dso [lindex $tie($tid) 1]
00338         $dso setv $idx $value
00339     }
00340     } else {
00341     #puts "%% unlock/1 $mid,$idx"
00342     unset -nocomplain lock($mid,$idx)
00343     return -code error "Bad trace call, unexpected operation \"$op\""
00344     }
00345 
00346     #puts "%% unlock/2 $mid,$idx"
00347     unset -nocomplain lock($mid,$idx)
00348     return
00349 }
00350 
00351 ret  ::tie::Connect (type avar , type open , type merge , type dso) {
00352     upvar 1 $avar thearray
00353 
00354     # Doing this as first operation is a convenient check that the ds
00355     # object command exists.
00356     set dsdata [$dso get]
00357  
00358     if {$open} {
00359     # Open DS and load data from it.
00360 
00361     # Save current contents of array, for restoration in case of
00362     # trouble.
00363     set save [array get thearray]
00364 
00365     if {$merge} {
00366         # merge -> Remember the existing keys, so that we
00367         # save their contents after loading the DS as well.
00368         set wback [array names thearray]
00369     } else {
00370         # not merge -> Replace existing content.
00371         array unset thearray *
00372     }
00373 
00374     if {[set code [catch {
00375         array set thearray $dsdata
00376         # ! Propagation through other ties.
00377     } msg]]} {
00378         # Errors found. Reset bogus contents, then reinsert the
00379         # saved information to restore the previous state.
00380         array unset thearray *
00381         array set thearray $save
00382 
00383         return -code $code \
00384             -errorcode $::errorCode \
00385             -errorinfo $::errorInfo $msg
00386     }
00387 
00388     if {$merge} {
00389         # Now save everything we had before the tie was added into
00390         # the DS. This may save data which came from the DS.
00391         foreach idx $wback {
00392         $dso setv $idx $thearray($idx)
00393         }
00394     }
00395     } else {
00396     # Save array data to DS.
00397 
00398     # Save current contents of DS, for restoration in case of
00399     # trouble.
00400     # set save $dsdata
00401 
00402     set source [array get thearray]
00403 
00404     if {$merge} {
00405         # merge -> Remember the existing keys, so that we
00406         # read their contents after saving the array as well.
00407         set rback [$dso names]
00408     } else {
00409         # not merge -> Replace existing content.
00410         $dso unset
00411     }
00412 
00413     if {[set code [catch {
00414         $dso set $source
00415     } msg]]} {
00416         $dso unset
00417         $dso set $dsdata
00418 
00419         return -code $code \
00420             -errorcode $::errorCode \
00421             -errorinfo $::errorInfo $msg
00422     }
00423 
00424     if {$merge} {
00425         # Now read everything we had before the tie was added from
00426         # the DS. This may read data which came from the array.
00427         foreach idx $rback {
00428         set thearray($idx) [$dso getv $idx]
00429         # ! Propagation through other ties.
00430         }
00431     }
00432     }
00433     return
00434 }
00435 
00436 ret  ::tie::Untie (type mid , type avar) {
00437     variable mgr
00438     variable tie
00439     variable lock
00440 
00441     upvar 1 $avar thearray
00442 
00443     trace remove variable thearray \
00444         {write unset} \
00445         [list ::tie::Trace $mid]
00446 
00447     foreach tid $mgr($mid) {
00448     foreach {mid dso} $tie($tid) break
00449     # ASSERT: mid == mid
00450 
00451     unset tie($tid)
00452     $dso destroy
00453     }
00454 
00455     unset mgr($mid)
00456     array unset lock ${mid},*
00457     return
00458 }
00459 
00460 /*  ### ### ### ######### ######### #########*/
00461 /*  Test helper, peek into internals*/
00462 /*  Returns a serialized representation.*/
00463 
00464 ret  ::tie::Peek () {
00465     variable mgr
00466     variable tie
00467 
00468     variable mgrcount
00469     variable tiecount
00470 
00471     list \
00472         $mgrcount $tiecount \
00473         mgr [Dictsort [array get mgr]] \
00474         tie [Dictsort [array get tie]]
00475 }
00476 
00477 ret  ::tie::Reset () {
00478     variable mgrcount 0
00479     variable tiecount 0
00480     return
00481 }
00482 
00483 ret  ::tie::Dictsort (type dict) {
00484     array set a $dict
00485     set out [list]
00486     foreach key [lsort [array names a]] {
00487     lappend out $key $a($key)
00488     }
00489     return $out
00490 }
00491 
00492 /*  ### ### ### ######### ######### #########*/
00493 /*  Standard DS classes*/
00494 /*  @mdgen NODEP: tie::std::log*/
00495 /*  @mdgen NODEP: tie::std::dsource*/
00496 /*  @mdgen NODEP: tie::std::array*/
00497 /*  @mdgen NODEP: tie::std::rarray*/
00498 /*  @mdgen NODEP: tie::std::file*/
00499 /*  @mdgen NODEP: tie::std::growfile*/
00500 
00501 ::tie::register {package require tie::std::log      ; ::tie::std::log}      as log
00502 ::tie::register {package require tie::std::dsource  ; ::tie::std::dsource}  as dsource
00503 ::tie::register {package require tie::std::array    ; ::tie::std::array}    as array
00504 ::tie::register {package require tie::std::rarray   ; ::tie::std::rarray}   as remotearray
00505 ::tie::register {package require tie::std::file     ; ::tie::std::file}     as file
00506 ::tie::register {package require tie::std::growfile ; ::tie::std::growfile} as growfile
00507 
00508 /*  ### ### ### ######### ######### #########*/
00509 /*  Ready to go*/
00510 
00511 package provide tie 1.1
00512 

Generated on 21 Sep 2010 for Gui by  doxygen 1.6.1