#!/bin/env tclsh
package require sha256
package require sqlite3
proc readbak {path} {
set fd [open $path rb]
set magic [read $fd 6]
if {$magic != "AMEBAK"} {
close $fd
return -code error "invalid magic string: \"$magic\""
}
set ver [read $fd 1]
binary scan $ver c ver
if {$ver != 0} {
close $fd
return -code error "archive file too new: $ver (self: 0)"
}
set mode [read $fd 1]
read $fd 8; # skip filesize
switch -- $mode {
r {}
F {
zlib push inflate $fd -level 9
}
default {
close $fd
return -code error "unknown compression mode: $mode"
}
}
set ret [read $fd]
close $fd
return $ret
}
proc readstore {store hash {hex 0}} {
if {!$hex} { set hexhash [binary encode hex $hash] }
set path $store
append path "/[string range $hexhash 0 1]"
append path "/[string range $hexhash 2 3]"
append path "/[string range $hexhash 4 5]/"
append path "/[string range $hexhash 6 end]"
if {[file exists $path]} {
return [readbak $path]
} else {
return -code error "missing file"
}
}
proc writebak {path data} {
binary scan $data c _
set bench [string range $data 0 10000000]
binary scan $bench c _
set Fbench [zlib deflate $bench 9]
set fd [open $path wb]
puts -nonewline $fd "AMEBAK\0"
set compress 0
if {[string length $Fbench] < ([string length $bench] * 95 / 100)} {
puts -nonewline $fd "F"
set compress 1
} else { puts -nonewline $fd "r" }
puts -nonewline $fd [binary format W [string length $data]]
if {$compress} { zlib push deflate $fd -level 9 }
puts -nonewline $fd $data
close $fd
}
proc writestore {store data {hex 0}} {
set hash [::sha2::sha256 -bin -- $data]
set hexhash [binary encode hex $hash]
set path $store
append path "/[string range $hexhash 0 1]"
append path "/[string range $hexhash 2 3]"
append path "/[string range $hexhash 4 5]/"
file mkdir $path
set path "$path/[string range $hexhash 6 end]"
if {![file exists $path]} {
writebak $path $data
}
if {$hex} {
return $hexhash
} else {
return $hash
}
}
proc statbak {path} {
set fd [open $path rb]
set magic [read $fd 6]
if {$magic != "AMEBAK"} {
close $fd
return -code error "invalid magic string: \"$magic\""
}
set ver [read $fd 1]
binary scan $ver c ver
if {$ver != 0} {
close $fd
return -code error "archive file too new: $ver (self: 0)"
}
set mode [read $fd 1]
switch -- $mode {
r { set mode raw }
F { set mode deflate }
default {
close $fd
return -code error "unknown compression mode: $mode"
}
}
set disksize [file size $path]
binary scan [read $fd 8] W datasize
close $fd
return [list ver $ver mode $mode disksize $disksize datasize $datasize]
}
proc statstore {store hash {hex 0}} {
if {$$hex} { set hexhash [binary encode hex $hash] }
set path $store
append path "/[string range $hexhash 0 1]"
append path "/[string range $hexhash 2 3]"
append path "/[string range $hexhash 4 5]/"
append path "/[string range $hexhash 6 end]"
if {![file exists $path]} return
statbak $path
}
proc usage {} {
global argv0
puts stderr "usage: $argv0 command store ?args ...?"
puts stderr "commands:"
puts stderr " init - initialize a store in store"
puts stderr " importlist ?listfile? - if listfile is ommitted, reads stdin"
exit -1
}
proc die {reason} {
global argv0
puts stderr "$argv0: fatal: $reason"
exit -2
}
if {[llength $argv] < 2} usage
set argv [lassign $argv command store]
set dbpath "$store/index.db"
set arpath "$store/archive"
if {$command == "init"} {
if {[file exists $dbpath]} { die "won't overwrite \"$dbpath\"." }
file mkdir $store
file mkdir $arpath
sqlite3 db $dbpath
db eval {
CREATE TABLE tarballs (name TEXT NOT NULL, type TEXT NOT NULL, ref TEXT NOT NULL, desc TEXT NOT NULL, date TEXT NOT NULL);
}
exit
}
if {[catch {sqlite3 db $dbpath -create false}]} { die "can't find a store at \"$store\"." }
switch -- $command {
importlist {
if {[llength $argv] > 1} usage
if {[llength $argv] == 1} {
set fd [open [lindex $argv 0]]
set fname [lindex $argv 0]
} else {
set fd stdin
set fname "pipeline"
}
set files [split [string trim [read $fd]] "\n"]
set filelist {}
close $fd
foreach filename $files {
if {![file exists $filename]} {
puts stderr "warning: \"$filename\" doesn't exist"
continue
}
set fd [open $filename rb]
set data [read $fd]
close $fd
set hash [writestore $arpath $data 1]
lappend filelist [string trimleft $filename "/"] $hash
}
set ref [writestore $arpath $filelist 1]
set date [clock seconds]
set rowid [db eval {
INSERT INTO tarballs VALUES ($fname, "list", $ref, "", $date) RETURNING rowid;
}]
puts stderr "inserted as archive $rowid"
}
}