Posted to tcl by makr at Fri Nov 09 11:55:32 GMT 2007view raw
- proc ::uncompress::channelFilter {op ichan args} {
- variable cfMap
- # cfMap keys:
- # iposition = amount of data read from underlying input channel
- # ichan = the underlying input channel
- # header = decoded gzip header
- # command = zlib stream command to inflate input
- # bsize = buffer size used in zlib stream command
- # idata = last chunk of data read from input stream (always at least 8 byte)
- # position = amount of data transfered already
- # ocrc32 = CRC32 of (so far) inflated data
- # eoi = end of input reached?
- # buffer = already inflated, but not yet transfered data
- # isize = size of original data, only available after eoi
- # icrc32 = CRC32 of original data, only available after eoi
- set result ""
- if {$op eq "Attach"} {
- # not supported by [rechan]
- # Create a new stacked channel using this command's provided filter
- # functionality and initiallize all data structures.
- package require zlib
- # be sure the input channel is readable!
- set bsize [lindex $args 0]
- # sanitize buffer size
- if {![string is integer -strict $bsize]} {
- set bsize 4096
- } elseif {$bsize < 1024} {
- set bsize 1024
- } elseif {$bsize > 1048576} {
- set bsize 1048576
- }
- fconfigure $ichan -translation binary -buffering full -buffersize $bsize
- set header [GzipHeader $ichan pos]
- set cmd gunzip[incr cfMap(sequence)]
- zlib sinflate $cmd $bsize
- set ochan [rechan [namespace code channelFilter] 2]
- set cfMap($ochan,iposition) $pos
- set cfMap($ochan,ichan) $ichan
- set cfMap($ochan,header) $header
- set cfMap($ochan,command) $cmd
- set cfMap($ochan,bsize) $bsize
- set cfMap($ochan,idata) ""
- set cfMap($ochan,position) 0
- set cfMap($ochan,ocrc32) 0
- set cfMap($ochan,eoi) 0
- set cfMap($ochan,buffer) ""
- set result $ochan
- } elseif {$op eq "Cget"} {
- # not supported by [rechan]
- # Return the value of the requested data structure item.
- set item [lindex $args 0]
- set result $cfMap($ichan,$item)
- } elseif {$op eq "Cgetall"} {
- # not supported by [rechan]
- # Return the channel filter's data structure as list suitable for
- # [array set].
- foreach k [lsort [array names cfMap $ichan,*]] {
- lappend result [string map [list $ichan, ""] $k] $cfMap($k)
- }
- } elseif {$op eq "read"} {
- # supported by [rechan]
- # $count is the channel's configured buffersize
- # If the buffer can fulfill the request, get from buffer without
- # refilling. Otherwise fill with data from input channel, and drain
- # into buffer first.
- set count [lindex $args 0]
- while {$count > [string length $cfMap($ichan,buffer)]} {
- if {([$cfMap($ichan,command) fill] == 0) &&
- !$cfMap($ichan,eoi)} {
- # always preserve the last 8 byte (gzip footer)
- set cfMap($ichan,idata) \
- [string range $cfMap($ichan,idata) end-8 end]
- set idata [read $cfMap($ichan,ichan) $cfMap($ichan,bsize)]
- append cfMap($ichan,idata) $idata
- incr cfMap($ichan,iposition) [string length $idata]
- if {[eof $cfMap($ichan,ichan)]} {
- GzipFooter $cfMap($ichan,idata) \
- cfMap($ichan,icrc32) cfMap($ichan,isize)
- set cfMap($ichan,eoi) 1
- }
- $cfMap($ichan,command) fill $idata
- }
- set odata [$cfMap($ichan,command) drain $cfMap($ichan,bsize)]
- if {[string length $odata]} {
- set cfMap($ichan,ocrc32) \
- [zlib crc32 $odata $cfMap($ichan,ocrc32)]
- append cfMap($ichan,buffer) $odata
- } else {
- break
- }
- }
- set result [string range $cfMap($ichan,buffer) 0 [expr {$count - 1}]]
- incr cfMap($ichan,position) [string length $result]
- set cfMap($ichan,buffer) \
- [string range $cfMap($ichan,buffer) $count end]
- } elseif {$op eq "close"} {
- # supported by [rechan]
- # delete gunzip command, close input channel,
- # and dissolve channel filter information
- rename $cfMap($ichan,command) {}
- close $cfMap($ichan,ichan)
- array unset cfMap $ichan,*
- } else {
- return -code error \
- "unknown operation, must be Attach, Cget, Cgetall, read, or close"
- }
- return $result
- }
- proc ::uncompress::GzipHeader {ichan tellvar} {
- upvar 1 $tellvar iread
- array set len {
- head 10
- fextra 2
- fhcrc 2
- }
- set iread 0
- set blocked [fconfigure $ichan -blocking]
- fconfigure $ichan -blocking 1
- set head [read $ichan $len(head)]
- incr iread $len(head)
- binary scan $head H4cb8icc magic cm flg mtime xfl os
- if {$magic ne "1f8b"} {
- return -code error "input is not gzipped"
- }
- if {$cm != 8} {
- return -code error "unsupported compression: input is not deflated"
- }
- set result [list head $head magic $magic cm $cm flg $flg mtime $mtime xflg $xfl os $os]
- # RFC1952: xfl = 2: slowest; xfl = 4: fastest - but unused in the wild
- # for table of os, see RFC1952 - uninteressting here
- foreach {FTEXT FHCRC FEXTRA FNAME FCOMMENT R1 R2 R3} [split $flg ""] {break}
- # if one of these is set, it may indicate additional extra fields,
- # we don't know how to handle
- if {$R1 || $R2 || $R3} {
- return -code error "unsupported flags: reserved flags are set and may\
- indicate an unsupported gzip data format"
- }
- if {$FEXTRA} {
- binary scan [read $ichan $len(fextra)] S xlen
- incr iread $len(fextra)
- set extra [read $ichan $xlen]
- incr iread $xlen
- # extra now contains some subfields, which may specify additional data,
- # see RFC1952 and http://www.gzip.org/format.txt
- } else {
- set extra ""
- }
- lappend result extra $extra
- if {$FNAME} {
- set c [read $ichan 1]
- incr iread
- while {$c != "\x0"} {
- append name $c
- set c [read $ichan 1]
- incr iread
- }
- # the name is stored with iso8859-1 encoding
- set name [encoding convertfrom iso8859-1 $name]
- } else {
- set name ""
- }
- lappend result name $name
- if {$FCOMMENT} {
- set c [read $ichan 1]
- incr iread
- while {$c != "\x0"} {
- append comment $c
- set c [read $ichan 1]
- incr iread
- }
- # the comment is stored with iso8859-1 encoding
- # [FIXME] do a conversion if this information should be used somewhere
- } else {
- set comment ""
- }
- lappend result comment $comment
- if {$FHCRC} {
- set crc16 [read $ichan $len(fhcrc)]
- incr iread $len(fhcrc)
- } else {
- set crc16 ""
- }
- lappend result crc16 $crc16 tell $iread
- fconfigure $ichan -blocking $blocked
- return $result
- }
- proc ::uncompress::GzipFooter {idata icrc32var isizevar} {
- upvar 1 $icrc32var icrc32 $isizevar isize
- # calculate and read gzip footer
- set ifoot [expr {[string length $idata] - 8}]
- binary scan $idata x${ifoot}ii icrc32 isize
- # This is needed for systems where int is 64bit wide. From binary scan man
- # page: Note that the integers returned are signed, but they can be
- # converted to unsigned 32-bit quantities using an expression like:
- if {[expr {$icrc32 >> 32}] == -1} {
- set icrc32 [expr {$icrc32 & 0xffffffff}]
- }
- return [list crc32 $icrc32 size $isize]
- }
- # here is how to use it ...
- proc ::uncompress::gunZip {args} {
- arguments {
- {input -any "" "Name of gzipped file"}
- {?output? -string "" "Name of destination file"}
- {-progress -string "" "var to store inflation progress into"}
- {-buffer -int 65536 "read buffer size"}
- }
- # open and configure input
- set ifd [channelFilter gzip Attach [open $input r]]
- fconfigure $ifd -translation binary -blocking 1 -buffering full -buffersize $buffer
- # init progress reporting
- if {$progress ne ""} {
- set withProgress 1
- upvar 1 $progress Progress
- set Progress 0
- set inlen [file size $input] ;# uncatched
- } else {
- set withProgress 0
- }
- # sanitize buffer size
- if {$buffer < 1024} {
- set buffer 1024
- } elseif {$buffer > 1048576} {
- set buffer 1048576
- }
- array set header [channelFilter gzip Cget $ifd header]
- set mtime $header(mtime)
- set name $header(name)
- # generate output filename if necessary
- if {($output eq "") && ([set output $name] eq "") &&
- ![regsub -- {\.[gG][zZ]$} $input {} output]} {
- return -code error "input filename does not end with \"gz\" and there\
- was no original name stored in the file"
- }
- # ensure $output is not the same file as $input, fail if so
- if {[file exists $output] &&
- ([file normalize $input] eq [file normalize $output])} {
- return -code error "refusing to overwrite input file"
- }
- set ofd [open $output w]
- fconfigure $ofd -translation binary -buffering full
- if {$withProgress} {
- set Progress [expr {100.0 / $inlen * [channelFilter gzip Cget $ifd iposition]}]
- }
- while {![eof $ifd]} {
- puts -nonewline $ofd [read $ifd $buffer]
- if {$withProgress} {
- set Progress [expr {100.0 / $inlen * [channelFilter gzip Cget $ifd iposition]}]
- }
- }
- close $ofd
- set icrc32 [channelFilter gzip Cget $ifd icrc32]
- set ocrc32 [channelFilter gzip Cget $ifd ocrc32]
- if {$icrc32 != $ocrc32} {
- # huh? checksum mismatch
- return -code error "CRC32 mismatch on output data"
- }
- close $ifd
- if {$mtime} {
- file mtime $output $mtime
- }
- if {$withProgress} {
- set Progress 100
- }
- }
Comments
Posted by dgroth at Thu Dec 13 14:27:59 GMT 2007 [text] [code]
where is the proc arguments defined ?