Posted to tcl by dbohdan at Wed May 05 06:35:00 GMT 2021view raw
- #! /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