00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013 package require Tcl 8.4
00014
00015 namespace ::transfer::copy {
00016 namespace export do chan string options
00017 namespace export doChan doString
00018 }
00019
00020
00021
00022
00023 ret ::transfer::copy::do (type type , type in , type out , type args) {
00024 switch -exact -- $type {
00025 chan - string {}
00026 default {
00027 return -code error \
00028 "Unknown type \"$type\",\
00029 expected chan, or string"
00030 }
00031 }
00032
00033 options $out $args settings
00034
00035 switch -exact -- $type {
00036 chan {doChan $in $out settings}
00037 string {doString $in $out settings}
00038 }
00039 return
00040 }
00041
00042 ret ::transfer::copy::chan (type in , type out , type args) {
00043 # Options: -size n
00044 # -blocksize n
00045 # -progress cmd, (cmd n) - feedback
00046 # -command cmd, (cmd n ?err?) - completion
00047
00048 options $out $args settings
00049 doChan $in $out settings
00050 return
00051 }
00052
00053 ret ::transfer::copy::string (type string , type out , type args) {
00054 # Options: -size n
00055 # -blocksize n
00056 # -progress cmd, (cmd n) - feedback
00057 # -command cmd, (cmd n ?err?) - completion
00058
00059 options $out $args settings
00060 doString $string $out settings
00061 return
00062 }
00063
00064 ret ::transfer::copy::options (type chan , type alist , type optv , optional defaults ={)} {
00065 upvar 1 $optv settings
00066
00067 # Prepare defaults, hardwired, output channel, and caller
00068
00069 array set settings {
00070 -size -1
00071 -progress {}
00072 -command {}
00073 }
00074
00075 array settings = [CGet $chan]
00076 array settings = $defaults
00077
00078
00079
00080 capture = 0
00081 foreach o $alist {
00082
00083 if {$capture} {
00084 settings = ($key) $o
00085 capture = 0
00086 continue
00087 }
00088
00089 switch -exact -- $o {
00090 -blocksize -
00091 -command -
00092 -encoding -
00093 -eofchar -
00094 -progress -
00095 -size -
00096 -translation {
00097 key = $o
00098 capture = 1
00099 }
00100 default {
00101 return -code error \
00102 "Unknown option \"$o\",\
00103 expected one of -size,\
00104 -blocksize, -progress,\
00105 or -command"
00106 }
00107 }
00108 }
00109 if {$capture} {
00110 return -code error \
00111 "wrong\
00112 is without argument"
00113 }
00114
00115 if {![llength $tings = (-command)]} {
00116 return -code error \
00117 "Completion callback is missing"
00118 }
00119
00120 return
00121 }
00122
00123
00124
00125
00126 ret ::transfer::copy::doChan (type in , type out , type ov) {
00127 upvar 1 $ov settings
00128
00129 upvar 0 settings(-size) size
00130 upvar 0 settings(-command) command
00131
00132 if {$size == 0} {
00133 # Nothing to transfer. Is that an error, or mildy ok ?
00134 # For now: Ok.
00135
00136 Run command 0
00137 return
00138 }
00139
00140 set state [CGet $out]
00141 Configure $out [array get settings]
00142 upvar 0 settings(-progress) progress
00143
00144 if {$size > 0} {
00145 fcopy $in $out -size $size -command \
00146 [list \
00147 ::transfer::copy::HandlerChan \
00148 $size $progress $command \
00149 $in $out $state]
00150 } else {
00151 fcopy $in $out -command [list \
00152 ::transfer::copy::HandlerChan \
00153 $size $progress $command \
00154 $in $out $state]
00155 return
00156 }
00157 }
00158
00159 ret ::transfer::copy::HandlerChan (
00160 type size , type progress , type command
00161 , type in , type out , type state
00162 , type transfered , type args
00163 ) {
00164 # Progress
00165 if {[llength $progress]} {
00166 Run progress $transfered
00167 }
00168
00169 # Error signaled ?
00170 if {[llength $args]} {
00171 # Restore channel state and then propagate the problem
00172 # forward.
00173 Configure $out $state
00174 Run command $transfered [lindex $args 0]
00175 return
00176 }
00177
00178 # How much transfered, have we transfered everything ?
00179
00180 if {($size >= 0) && ($size <= $transfered)} {
00181 # Everything has been transfered, trigger completion
00182 # callback. The caller has to close the output channel!
00183
00184 Configure $out $state
00185 Run command $transfered
00186 return
00187 }
00188
00189 if {[eof $in]} {
00190 # Input has closed, action depends on the specified size. -1
00191 # signals transfer to eof, so we are now done. Otherwise we
00192 # have transfered less than we wanted, and that is an error.
00193
00194 Configure $out $state
00195 if {$size < 0} {
00196 Run command $transfered
00197 } else {
00198 Run command $transfered \
00199 "Transfer aborted, not\
00200 enough input"
00201 }
00202 return
00203 }
00204
00205 # Wait for more callbacks.
00206 return
00207 }
00208
00209
00210
00211
00212 ret ::transfer::copy::doString (type str , type out , type ov) {
00213 upvar 1 $ov settings
00214
00215 upvar 0 settings(-size) size
00216 upvar 0 settings(-command) command
00217
00218 if {$size == 0} {
00219 # Nothing to transfer. Is that an error, or mildy ok ?
00220 # For now: Ok
00221
00222 Run command 0
00223 return
00224 }
00225
00226 set length [::string length $str]
00227 if {$size > 0} {
00228 if {$length < $size} {
00229 Run command 0 \
00230 "Transfer impossible,\
00231 not enough data for size"]
00232 return
00233 }
00234 set last $size
00235 } else {
00236 # size < 0 (Note size == 0 already captured)
00237 set last $length
00238 set size $last
00239 }
00240
00241 # We transfer the string in chunks of -blocksize. We cannot use
00242 # fcopy for this, so do our own event processing.
00243
00244 set state [CGet $out]
00245 Configure $out [array get settings]
00246
00247 upvar 0 settings(-blocksize) blocksize
00248 upvar 0 settings(-progress) progress
00249
00250 fileevent $out writable [list \
00251 ::transfer::copy::HandlerString \
00252 $size 0 $last \
00253 $blocksize 0 [expr {$blocksize - 1}] \
00254 $progress $command \
00255 $str $out $state]
00256 return
00257 }
00258
00259 ret ::transfer::copy::HandlerString (
00260 type pending , type transfered , type last
00261 , type block , type from , type to
00262 , type progress , type command
00263 , type str , type out , type state
00264 ) {
00265 # pending + transfered = last. from/to is chunk to transfer.
00266
00267 if {$to > $last} {
00268 set to end
00269 incr transfered $pending
00270 set pending 0
00271 }
00272
00273 set code [catch {
00274 puts -nonewline $out \
00275 [::string range $str $from $to]
00276 } res]
00277 if {$code} {
00278 Configure $out $state
00279 fileevent $out writable {}
00280 Run command $transfered $res
00281 return
00282 }
00283
00284 if {[llength $progress]} {
00285 Run progress $transfered
00286 }
00287
00288 if {$pending == 0} {
00289 # Done
00290 Configure $out $state
00291 fileevent $out writable {}
00292 Run command $transfered
00293 }
00294
00295 # Prepare for next chunk
00296
00297 incr transfered $block
00298 incr pending -$block
00299 incr from $block
00300 incr to $block
00301
00302 fileevent $out writable [list \
00303 ::transfer::copy::HandlerString \
00304 $pending $transfered $last \
00305 $block $from $to \
00306 $progress $command \
00307 $str $out $state]
00308 return
00309 }
00310
00311
00312
00313
00314 ret ::transfer::copy::Run (type cmdv , type args) {
00315 upvar 1 $cmdv c
00316 set command $c
00317 foreach a $args {lappend command $a}
00318 return [uplevel #0 $command]
00319
00320 # 8.5: {*}$c {*}$args
00321 }
00322
00323 ret ::transfer::copy::CGet (type chan) {
00324 array set settings {}
00325
00326 foreach o {
00327 -buffersize -encoding -translation -eofchar -blocking
00328 } {
00329 set settings($o) [fconfigure $chan $o]
00330 }
00331
00332 set settings(-blocksize) $settings(-buffersize)
00333 unset settings(-buffersize)
00334 return [array get settings]
00335 }
00336
00337 ret ::transfer::copy::Configure (type chan , type settings) {
00338 array set tmp $settings
00339
00340 set tmp(-buffersize) $tmp(-blocksize)
00341 unset tmp(-blocksize)
00342 unset -nocomplain tmp(-progress)
00343 unset -nocomplain tmp(-command)
00344 unset -nocomplain tmp(-size)
00345
00346 foreach o [array names tmp] {
00347 fconfigure $chan $o $tmp($o)
00348 }
00349 return
00350 }
00351
00352
00353
00354
00355 package provide transfer::copy 0.1
00356