Version 1 of Zip A Directory

Updated 2012-07-09 16:18:36 by tomk

tjk This code is a modified version of the zipper code found at (http://equi4.com/critlib/ )

if 0 {

Creating ZIP archives in Tcl
============================

  Rev 0.20: modified code so it will create empty directories and
            extended code with directory compress command (tjk)
  Rev 0.12: Added code to create directories (thx, Wojciech Kocjan)
  Rev 0.11: Added ?force? arg to bypass re-compression
  Rev 0.10: Initial release


Zipper is a package to create ZIP archives with a few simple commands:

    zipper::initialize $fd
        initialize things to start writing zip file entries

    zipper::addentry name contents ?date? ?force?
        add one entry, modification date defaults to [clock seconds]

    zipper::adddirectory name ?date? ?force?
        add directory entry, modification date defaults to [clock seconds]

    zipper::finalize
        write trailing table of contents, returns file descriptor

    zipper::compress fname fname.zip
        zip the contents of fname to fname.zip, fname can be eather a
        file or directory

Example:

    package require zipper
    zipper::initialize [open try.zip w]
    zipper::addentry dir/file.txt "some data to store"
    close [zipper::finalize]

}

package provide zipper 0.2
package require vfs::zip

namespace eval zipper {

    namespace ensemble create
    namespace export compress
    
    proc compress { from to } {
        set cwd [pwd]
        set target [file tail ${from}]
        set dirs ${target}
        cd [file dirname ${from}]
        initialize [open ${to} w]
        while { [llength ${dirs}] > 0 } {
            set d [lindex ${dirs} 0]
            set dirs [lrange ${dirs} 1 end]
            foreach f [lsort [glob -nocomplain [file join ${d} *]]] {
                if { [file isfile ${f}] } {
                    regsub {^\./} ${f} {} f
                    set fd [open ${f}]
                    fconfigure ${fd} -translation binary -encoding binary
                    addentry ${f} [read ${fd}] [file mtime ${f}]
                    close ${fd}
                } elseif { [file isdir ${f}] } {
                    adddir ${f} [file mtime ${f}]
                    lappend dirs ${f}
                }
            }
        }
        close [finalize]
        cd ${cwd}
    }

    namespace eval v {
        variable fd
        variable base
        variable toc
    }

    proc initialize {fd} {
        set v::fd $fd
        set v::base [tell $fd]
        set v::toc {}
        fconfigure $fd -translation binary -encoding binary
    }

    proc emit {s} {
        puts -nonewline $v::fd $s
    }

    proc dostime {sec} {
        set f [clock format $sec -format {%Y %m %d %H %M %S} -gmt 1]
        regsub -all { 0(\d)} $f { \1} f
        foreach {Y M D h m s} $f break
        set date [expr {(($Y-1980)<<9) | ($M<<5) | $D}]
        set time [expr {($h<<11) | ($m<<5) | ($s>>1)}]
        return [list $date $time]
    }

    proc addentry {name contents {date ""} {force 0}} {
        if {$date == ""} { set date [clock seconds] }
        lassign [dostime $date] date time
        set flag 0
        set type 0 ;# stored
        set fsize [string length $contents]
        set csize $fsize
        set fnlen [string length $name]
        
        if {$force > 0 && $force != [string length $contents]} {
            set csize $fsize
            set fsize $force
            set type 8 ;# if we're passing in compressed data, it's deflated
        }
        
        if {[catch { zlib crc32 $contents } crc]} {
            set crc 0
        } elseif {$type == 0} {
            set cdata [zlib deflate $contents]
            if {[string length $cdata] < [string length $contents]} {
                set contents $cdata
                set csize [string length $cdata]
                set type 8 ;# deflate
            }
        }
        
        lappend v::toc "[binary format a2c6ssssiiiss4ii PK {1 2 20 0 20 0} \
        $flag $type $time $date $crc $csize $fsize $fnlen \
        {0 0 0 0} 128 [tell $v::fd]]$name"
        
        emit [binary format a2c4ssssiiiss PK {3 4 20 0} \
        $flag $type $time $date $crc $csize $fsize $fnlen 0]
        emit $name
        emit $contents
    }

    proc adddir {name {date ""} {force 0}} {
        set name "${name}/"
        if {$date == ""} { set date [clock seconds] }
        lassign [dostime $date] date time
        set flag 0
        set type 0 ;# stored
        set fsize 0
        set csize 0
        set fnlen [string length $name]
        
        set crc 0
        
        lappend v::toc "[binary format a2c6ssssiiiss4ii PK {1 2 20 0 20 0} \
        $flag $type $time $date $crc $csize $fsize $fnlen \
        {0 0 0 0} 128 [tell $v::fd]]$name"
        
        emit [binary format a2c4ssssiiiss PK {3 4 20 0} \
        $flag $type $time $date $crc $csize $fsize $fnlen 0]
        emit $name
    }

    proc finalize {} {
        set pos [tell $v::fd]
        
        set ntoc [llength $v::toc]
        foreach x $v::toc { emit $x }
        set v::toc {}
        
        set len [expr {[tell $v::fd] - $pos}]
        incr pos -$v::base
        
        emit [binary format a2c2ssssiis PK {5 6} 0 0 $ntoc $ntoc $len $pos 0]
        
        return $v::fd
    }
}