tie_file.tcl

Go to the documentation of this file.
00001 /*  tie_file.tcl --*/
00002 /* */
00003 /*  Data source: Files.*/
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_file.tcl,v 1.8 2006/09/19 23:36:18 andreas_kupries Exp $*/
00011 
00012 /*  ### ### ### ######### ######### #########*/
00013 /*  Requisites*/
00014 
00015 package require snit
00016 package require tie
00017 
00018 /*  ### ### ### ######### ######### #########*/
00019 /*  Implementation*/
00020 
00021 snit::type ::tie::std::file {
00022     /*  ### ### ### ######### ######### #########*/
00023     /*  Notes*/
00024 
00025     /*  This data source maintains an internal cache for higher*/
00026     /*  efficiency, i.e. to avoid having to go out to the slow file.*/
00027 
00028     /*  This cache is handled as follows*/
00029     /** 
00030      *# - All write operations invalidate the cache and write directly
00031      *#   to the file.
00032      *#
00033      *# - All read operations load from the file if the cache is
00034      *#   invalid, and from the cache otherwise
00035  */
00036 
00037     /*  This scheme works well in the following situations:*/
00038 
00039     /*  (a) The data source is created, and then only read from.*/
00040     /*  (b) The data source is created, and then only written to.*/
00041     /*  (c) The data source is created, read once, and then only*/
00042     /*      written to.*/
00043 
00044     /*  This scheme works badly if the data source is opened and then*/
00045     /*  randomly read from and written to. The cache is useless, as it*/
00046     /*  is continuously invalidated and reloaded.*/
00047 
00048     /*  This no problem from this developers POV of view however.*/
00049     /*  Consider the context. If you have this situation just tie the*/
00050     /*  DS to an array A after creation. The tie framework operates on*/
00051     /*  the DS in mode (c) and A becomes an explicit cache for the DS*/
00052     /*  which is not invalidated by writing to it. IOW this covers*/
00053     /*  exactly the situation the DS by itself is not working well for.*/
00054 
00055     /*  ### ### ### ######### ######### #########*/
00056     /*  Specials*/
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     /*  Release the channel to the journal file, should it be open.*/
00080     if {$chan ne ""} {close $chan}
00081     return
00082     }
00083 
00084     /*  ### ### ### ######### ######### #########*/
00085     /*  API : Data source methods*/
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     /*  Internal : Instance data*/
00139 
00140     variable chan {} ; /*  Channel to write the journal.*/
00141     variable path {} ; /*  Path to journal file.*/
00142 
00143     /*  Journal loading, and cache.*/
00144 
00145     variable count 0         ; /*  #Operations in the journal.*/
00146     variable cvalid 0        ; /*  Validity of the cache.*/
00147     variable cache -array {} ; /*  Cache for journal*/
00148 
00149     /*  Management of the cache: See notes at beginning.*/
00150 
00151     /*  ### ### ### ######### ######### #########*/
00152     /*  Internal: Loading from the journal.*/
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 /*  Ready to go*/
00267 
00268 ::tie::register ::tie::std::file as file
00269 package provide   tie::std::file 1.0.2
00270 

Generated on 21 Sep 2010 for Gui by  doxygen 1.6.1