tie_file.tcl
Go to the documentation of this file.00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015 package require snit
00016 package require tie
00017
00018
00019
00020
00021 snit::type ::tie::std::file {
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
00032
00033
00034
00035
00036
00037
00038
00039
00040
00041
00042
00043
00044
00045
00046
00047
00048
00049
00050
00051
00052
00053
00054
00055
00056
00057
00058 pragma -hastyperet s no
00059 pragma -hasinfo no
00060 pragma -simpledispatch yes
00061
00062 # ### ### ### ######### ######### #########
00063 ## API : Construction & Destruction
00064
00065 constructor (type thepath) {
00066 # Locate and open the journal file.
00067
00068 set path [::file normalize $thepath]
00069 if {[::file exists $path]} {
00070 set chan [open $path {RDWR EXCL APPEND}]
00071 } else {
00072 set chan [open $path {RDWR EXCL CREAT APPEND}]
00073 }
00074 fconfigure $chan -buffering none -encoding utf-8
00075 return
00076 }
00077
00078 destructor {
00079
00080 if {$chan ne ""} {close $chan}
00081 return
00082 }
00083
00084
00085
00086
00087 ret get () {
00088 if {![::file size $path]} {return {}}
00089 $self LoadJournal
00090 return [array get cache]
00091 }
00092
00093 ret set (type dict) {
00094 puts $chan [list array set $dict]
00095 $self Invalidate
00096 return
00097 }
00098
00099 ret unset (optional pattern =*) {
00100 puts $chan [list array unset $pattern]
00101 $self Invalidate
00102 return
00103 }
00104
00105 ret names () {
00106 if {![::file size $path]} {return {}}
00107 $self LoadJournal
00108 return [array names cache]
00109 }
00110
00111 ret size () {
00112 if {![::file size $path]} {return 0}
00113 $self LoadJournal
00114 return [array size cache]
00115 }
00116
00117 ret getv (type index) {
00118 if {![::file size $path]} {
00119 return -code error "can't read \"$index\": no such variable"
00120 }
00121 $self LoadJournal
00122 return $cache($index)
00123 }
00124
00125 ret setv (type index , type value) {
00126 puts $chan [list set $index $value]
00127 $self Invalidate
00128 return
00129 }
00130
00131 ret unsetv (type index) {
00132 puts $chan [list unset $index]
00133 $self Invalidate
00134 return
00135 }
00136
00137
00138
00139
00140 variable chan {} ;
00141 variable path {} ;
00142
00143
00144
00145 variable count 0 ;
00146 variable cvalid 0 ;
00147 variable cache -array {} ;
00148
00149
00150
00151
00152
00153
00154 ret LoadJournal () {
00155 if {$cvalid} return
00156 $self Replay
00157 $self Compact
00158 return
00159 }
00160
00161 ret Replay () {
00162 # Use a safe interp for the evaluation of the journal file.
00163 # (Empty safe for the hidden commands and the aliases we insert).
00164
00165 # Called for !cvalid, implies cache does not exist
00166
00167 set ip [interp create -safe]
00168 foreach c [$ip eval {info commands}] {
00169 if {$c eq "rename"} continue
00170 $ip eval [list rename $c {}]
00171 }
00172 $ip eval {rename rename {}}
00173
00174 interp alias $ip set {} $self Set
00175 interp alias $ip unset {} $self Unset
00176 interp alias $ip array {} $self Array
00177
00178 array set cache {}
00179 set count 0
00180
00181 $ip invokehidden -global source $path
00182 interp delete $ip
00183
00184 set cvalid 1
00185 return
00186 }
00187
00188 ret Compact () {
00189 # Compact the journal
00190
00191 #puts @@/2*$count/3*[array size temp]/=/[expr {2*$count >= 3*[array size temp]}]
00192
00193 # ASSERT cvalid
00194
00195 # do not compact <=>
00196 # 2*ops < 3*size <=>
00197 # ops < 3/2*size <=>
00198 # ops < 1.5*size
00199
00200 if {(2*$count) < (3*[array size cache])} return
00201
00202 ::file delete -force ${path}.new
00203 set new [open ${path}.new {RDWR EXCL CREAT APPEND}]
00204 fconfigure $new -buffering none -encoding utf-8
00205
00206 # Compress current contents into a single multi-key load operation.
00207 puts $new [list array set [array get cache]]
00208
00209 if {$::tcl_platform(platform) eq "windows"} {
00210 # For windows the open channels prevent us from
00211 # overwriting the old file. We have to leave
00212 # attackers a (small) window of opportunity for
00213 # replacing the file with something they own :(
00214 close $chan
00215 close $new
00216 ::file rename -force ${path}.new $path
00217 set chan [open ${path} {RDWR EXCL APPEND}]
00218 fconfigure $chan -buffering none -encoding utf-8
00219 } else {
00220 # Copy compacted journal over the existing one.
00221 ::file rename -force ${path}.new $path
00222 close $chan
00223 set chan $new
00224 }
00225 return
00226 }
00227
00228 ret Set (type index , type value) {
00229 set cache($index) $value
00230 incr count
00231 return
00232 }
00233
00234 ret Unset (type index) {
00235 unset cache($index)
00236 incr count
00237 return
00238 }
00239
00240 ret Array (type cmd , type detail) {
00241 # syntax : set dict
00242 # ...... : unset pattern
00243
00244 if {$cmd eq "set"} {
00245 array set cache $detail
00246 } elseif {$cmd eq "unset"} {
00247 array unset cache $detail
00248 } else {
00249 return -code error "Illegal command \"$cmd\""
00250 }
00251 incr count
00252 return
00253 }
00254
00255 ret Invalidate () {
00256 if {!$cvalid} return
00257 set cvalid 0
00258 unset cache
00259 return
00260 }
00261
00262
00263 }
00264
00265
00266
00267
00268 ::tie::register ::tie::std::file as file
00269 package provide tie::std::file 1.0.2
00270