# -*- tcl -*- # (C) 2006 Andreas Kupries ## # ### package require Tcl 8.3 package require snit package require tie # ### snit::type pregistry { # API # delete key ?attribute? # mtime key ?attribute? # get key attribute # keys key ?pattern?/* # set key ?attribute value? # attrs key ?pattern? option -tie -default {} -configuremethod TIE ; # Persistence constructor {args} { $self configurelist $args $self INIT return } # ### method delete {key args} { #puts DEL|$key| if {[llength $args] > 1} {return -code error "wrong\#args"} if {[catch {NODE $key} n]} return if {[llength $args]} { # Delete attribute set attr [lindex $args 0] set pattern [list A $n $attr *] set km [list N $n M] array unset data $pattern set data($km) [clock seconds] } else { # Delete key and children. #puts N|$n| if {![llength $key]} { return -code error "cannot delete root" } # Children first foreach c [array names data [list C $n *]] { set c [lindex $c end] #puts _|$c| $self delete [linsert $key end $c] } # And now the node itself. Modify the parent as well, # remove this node as a child. set self [lindex $key end] set pidx [list N $n P] set npat [list N $n *] set apat [list A $n * *] set pid $data($pidx) set cidx [list C $pid $self] set midx [list N $pid M] array unset data $apat array unset data $npat unset -nocomplain data($cidx) set data($midx) [clock seconds] unset -nocomplain ncache($key) } return } method mtime {key args} { if {[llength $args] > 1} {return -code error "wrong\#args"} set n [NODE $key] if {[llength $args]} { set attr [lindex $args 0] set idx [list A $n $attr M] if {![info exists data($idx)]} { return -code error "Unknown attribute \"$attr\" in key \"$key\"" } } else { set idx [list N $n M] } return $data($idx) } method exists {key args} { if {[llength $args] > 1} { return -code error "wrong\#args" } elseif {[catch {NODE $key} n]} { return 0 } elseif {![llength $args]} { return 1 } set attr [lindex $args 0] set idx [list A $n $attr V] return [info exist data($idx)] } method get {key attr} { set n [NODE $key] set idx [list A $n $attr V] if {![info exists data($idx)]} { return -code error "Unknown attribute \"$attr\" in key \"$key\"" } return $data($idx) } method get||default {key attr default} { if {[catch {NODE $key} n]} { return $default } set idx [list A $n $attr V] if {![info exists data($idx)]} { return $default } return $data($idx) } method keys {key {pattern *}} { set n [NODE $key] set pattern [list C $n $pattern] set res {} foreach c [array names data $pattern] { lappend res [linsert $key end $c] } return $res } method attrs {key {pattern *}} { set n [NODE $key] set pattern [list A $n $pattern V] set res {} foreach c [array names data $pattern] { lappend res [lindex $c end-1] } return $res } method lappend {key attr value} { set list [$self get||default $key $attr {}] lappend list $value $self set $key $attr $list return } method set {key args} { set n [NODE $key 1] if {![llength $args]} return if {[llength $args] != 2} {return -code error "wrong\#args"} foreach {attr value} $args break # Ignore calls which do not change the contents of the # database. set aidx [list A $n $attr V] if { [info exists data($aidx)] && [string equal $data($aidx) $value] } return ; # {} #puts stderr "$n $attr | $key | ($value)" set aids [list A $n $attr M] set data($aidx) $value set data($aids) [clock seconds] return } # ### state variable data -array {} # Tree of keys. Each keys can have multiple attributes. # Each key, and attribute, have a modification timestamp. # Each node in the tree is identified by a numeric id. Children # refer to their parents. Parent id + name refers to unique child. # Array contents # (I) -> number id counter # (C id name) -> id parent id x name => child id # (N id P) -> id node id => parent id, empty for root # (N id M) -> timestamp node id => last modification # (A id name V) -> string node id x attribute name => value # (A id name M) -> timestamp s.a => last modification # This structure is less memory/space intensive than the setup of # 1registry. It is also more difficult to query as it is less # tabular, less redundant. # Another thing becoming more complex is the deletion of a # subtree. It is now necessary to walk the the tree, instead of # just deleting all keys in the array matching a certain # pattern. That at least can be done at the C level (array unset). # The conversion from key list to node is also linear in key # length, and an operation done often. Better cache it. However # only internally, or the space savingsare gone too as the space # is then taken by the conversion cache. Hm. Still less than # before, as each key is listed at most once. In 1registry it was # repeated for each of its attributes as well. This would regain # speed for searches, as the conversion cache now is a tabular # representation of the tree, and easily globbed. # ### configure -tie (persistence) method TIE {option value} { if {[string equal $options(-tie) $value]} return tie::untie [myvar data] # 8.5 - tie::tie [myvar data] {expand}$value eval [linsert $value 0 tie::tie [myvar data]] set options(-tie) $value return } method INIT {} { if {![info exists data(I)]} { set anchor {C {} {}} set rootp {N 0 P} set roots {N 0 M} set data(I) 0 set data($anchor) 0 set data($rootp) {} set data($roots) [clock seconds] } return } variable ncache -array {} proc NODE {key {create 0}} { upvar 1 ncache ncache data data if {[info exist ncache($key)]} { # Cached, shortcut return $ncache($key) } if {![llength $key]} { # Root, shortcut set id 0 } else { # Recursively convert, possibly create set parent [lrange $key 0 end-1] set self [lindex $key end] set pid [NODE $parent $create] set idx [list C $pid $self] if {[info exists data($idx)]} { set id $data($idx) } elseif {!$create} { return -code error "Unknown key \"$key\"" } else { set id [incr data(I)] set idxp [list N $id P] set idxm [list N $id M] set data($idx) $id set data($idxp) $pid set data($idxm) [clock seconds] } } set ncache($key) $id return $id } # ### } ## # ### package provide pregistry 0.1