Posted to tcl by dbohdan at Wed May 05 06:35:00 GMT 2021view pretty
#! /usr/bin/env tclsh
# Copyright (c) 2021 D. Bohdan and contributors.
# License: MIT.
# ==============================================================================
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
# ==============================================================================
package require Tcl 8.7
namespace eval ::pkgfs {
variable available {
{core tcllib 1.20+}
{
ref
QmRjsBWcu4W78F19vkXeBgwY5m8fpD2A6SHeSCCsAnHh5B
checksum
92479b4e7beb8e2682dac733fd5399252f00fe2219d5532d9065aeb634360c49
algo
sha256
}
{dbohdan autoopts 0.6.1}
{
ref
Qmea9ksHVd8sGTDuREX3jvgeymggnkE3eaWUkXEnS6Y5cH
checksum
0102ef871955d26ba17c42d0ec4fcb0e32f67c4b5f1a7b12880456dba199c095
algo
sha256
}
{dbohdan jimlib 0.15.0}
{
ref
QmSoxa3WhHdDUHQo7JjD4DVn5sUM9sxfDUrSQbhdBNuipQ
checksum
b413d8938818c4b100b968096a45a0686465bdd53392480a5bdb89ff11fb8329
algo
sha256
}
{de tdom 0.9.1 x86_64-linux-gnu}
{
ref
QmcC3T579hjjVb88VnM6DzTgSTbNNoqtSdvKzasRfTvCzP
checksum
fbea1a7654b667047fbee42ae5a96d3bc4c4213f0a692dea9120cf102d5887d4
algo
sha256
}
{drh wapp 1.0.0}
{
ref
QmUSNZqcQZUunP9gRn2kibz4TqAw1WAzXpdPizBFfQDFEq
checksum
86bbb01571ff21db275593d5366febd67fd47635b2c1db6085ef2db257b05c3f
algo
sha256
}
{mpcjanssen fcgi.tcl 0.5}
{
ref
QmeiVCPoBvk9KMU85vKkoAZWekFWggjAp2iAYPkQQpkqi7
checksum
8dd51684a42b489e53b5636280f435fa8161b1a6c51dec766fe87273a85d4a1d
algo
sha256
}
}
variable debug [dict getdef [array get ::env] PKGFS_DEBUG false]
variable version 0.2.0
}
namespace eval ::pkgfs::checksum {
variable command
set command(sha256) {}
}
namespace eval ::pkgfs::http {
variable client {}
variable timeout 60
}
namespace eval ::pkgfs::ipfs {
variable cacheDir [file normalize ~/.cache/pkgfs/]
variable currentGateway 0
variable gateways {
https://cloudflare-ipfs.com/ipfs/%s
https://ipfs.io/ipfs/%s
}
}
proc ::pkgfs::configure {} {
detect {
package require http
package require tls
http::register https 443 [list ::tls::socket -autoservername true]
set ::pkgfs::http::client tcl-http
} {
exec curl --version
set ::pkgfs::http::client curl-cli
} {
try {
exec busybox wget
} on error e {
if {![string match *--continue* $e]} {
error {can't execure busybox wget}
}
}
set ::pkgfs::http::client busybox-wget
} {
error {no usable HTTPS client}
}
detect {
exec sha256sum << {}
set ::pkgfs::checksum::command(sha256sum) sha256sum
} {
exec busybox sha256sum << {}
set ::pkgfs::checksum::command(sha256sum) {busybox sha256sum}
} {
error {no usable sha256sum}
}
}
proc ::pkgfs::detect args {
set errorScript [lindex $args end]
set args [lrange $args 0 end-1]
set ok false
foreach script $args {
try {
uplevel 1 $script
} on ok _ {
set ok true
break
} on error _ {}
}
if {!$ok} {
uplevel 1 $errorScript
}
}
proc ::pkgfs::grab-all {carts {tries 3}} {
foreach cart $carts {
grab $cart $tries
}
}
proc ::pkgfs::grab {cart {tries 3}} {
set pluggedIn false
lassign [name-to-ref $cart] metadata fullName
debug Grabbing cart $cart
debug Full name is $fullName
debug Ref is [dict get $metadata ref]
for {set i 1} {$i <= $tries} {incr i} {
debug Attempt number $i
try {
set f [ipfs::cache [dict get $metadata ref]]
debug Cart is in $f
plug-in $f
} trap {PKGFS HTTP DOWNLOAD} e {
debug HTTP client error: $e
} trap {TCL ZIPFS} e {
debug zipfs error: $e
file delete $f
} on ok _ {
set pluggedIn true
break
}
}
if {!$pluggedIn} {
return \
-code error \
-errorcode {PKGFS GRAB} \
[list failed to download and plug in cart $cart] \
}
debug inb4
set algo [dict get $metadata algo]
set actual [checksum::$algo $f]
debug done
set expected [dict get $metadata checksum]
if {$actual ne $expected} {
return \
-code error \
-errorcode {PKGFS GRAB} \
[list wrong $algo checksum $actual for cart $cart]
}
debug Plugged in $f
}
proc ::pkgfs::debug args {
variable debug
if {!$debug} return
set caller [dict get [info frame -1] proc]
puts stderr [format {PKGFS %-25s %s} $caller $args]
}
proc ::pkgfs::name-to-ref cart {
variable available
set len [llength $cart]
foreach k [dict keys $available] {
if {[lrange $k 0 $len-1] eq $cart} {
return [list [dict get $available $k] $k]
}
}
error [list unknown cart: $cart]
}
proc ::pkgfs::plug-in zip {
set new [index-dirs [mount $zip]]
lappend ::auto_path {*}$new
return $new
}
proc ::pkgfs::mount zip {
set root [zipfs root]
set dest $root[file rootname [file tail $zip]]
zipfs mount $dest $zip
return $dest
}
proc ::pkgfs::index-dirs path {
lmap f [zipfs list -glob $path*] {
if {[file tail $f] ne {pkgIndex.tcl}} continue
file dirname $f
}
}
proc ::pkgfs::checksum::sha256 path {
variable command
lindex [exec {*}$command(sha256sum) $path] 0
}
proc ::pkgfs::ipfs::cache src {
variable cacheDir
file mkdir $cacheDir
set dest [file join $cacheDir $src]
if {[file exists $dest]} {
::pkgfs::debug Destination exists
return $dest
}
::pkgfs::debug Downloading
download $src $dest
return $dest
}
proc ::pkgfs::ipfs::download {src dest} {
variable currentGateway
variable gateways
set gateway [lindex $gateways $currentGateway]
set currentGateway [expr { ($currentGateway + 1) % [llength $gateways] }]
::pkgfs::debug Chose gateway $gateway
::pkgfs::http::download [format $gateway $src] $dest
}
proc ::pkgfs::http::download {url dest} {
variable client
try {
::pkgfs::debug Download client: $client
$client $url $dest
} on error {e opts} {
dict set opts -errorcode {PKGFS HTTP DOWNLOAD}
return -code error {*}$opts $e
}
}
proc ::pkgfs::http::busybox-wget {url dest} {
variable timeout
# BusyBox wget never checks certificates.
exec busybox wget \
--no-check-certificate \
-O $dest \
-q \
$url \
}
proc ::pkgfs::http::curl-cli {url dest} {
variable timeout
exec curl \
--fail \
--location \
--max-time $timeout \
--output $dest \
--silent \
$url \
}
proc ::pkgfs::http::tcl-http {src dest} {
variable timeout
try {
set token [http::get $url -timeout ${timeout}000
set data [http::data $token]
set ch [open $dest wb]
puts -nonewline $ch $data
} finally {
catch { http::cleanup $token }
catch { close $ch }
}
}
::pkgfs::configure
package provide pkgfs 0