Index: client/1.0/pkgd.eagle ================================================================== --- client/1.0/pkgd.eagle +++ client/1.0/pkgd.eagle @@ -23,11 +23,21 @@ # # NOTE: This procedure sets up the default values for all configuration # parameters used by the package downloader client. There are no # arguments. # - proc setupDownloadVars {} { + proc setupDownloadVars { script } { + # + # NOTE: What is the fully qualified path to the directory containing the + # package downloader client? + # + variable clientDirectory + + if {![info exists clientDirectory]} then { + set clientDirectory [file dirname $script] + } + # # NOTE: Prevent progress messages from being displayed while downloading # from the repository, etc? By default, this is enabled. # variable quiet; # DEFAULT: true @@ -56,14 +66,24 @@ } # # NOTE: The root directory where any persistent packages will be saved. # - variable persistentDirectory; # DEFAULT: [getPersistentRootDirectory] + variable persistentRootDirectory; # DEFAULT: [getPersistentRootDirectory] + + if {![info exists persistentRootDirectory]} then { + set persistentRootDirectory [getPersistentRootDirectory] + } + + # + # NOTE: The root directory where any temporary packages will be written. + # + variable temporaryRootDirectory; # DEFAULT: [getFileTempDirectory PKGD_TEMP] - if {![info exists persistentDirectory]} then { - set persistentDirectory [getPersistentRootDirectory] + if {![info exists temporaryRootDirectory]} then { + set temporaryRootDirectory \ + [::PackageRepository::getFileTempDirectory PKGD_TEMP] } } # # NOTE: This procedure returns the root directory where any packages that @@ -75,10 +95,56 @@ # NOTE: Return a directory parallel to the one containing the library # directory. # return [file join [file dirname [info library]] pkgd] } + + # + # NOTE: This procedure returns non-zero if the specified file seems to be + # an OpenPGP signature file. The fileName argument is the name of + # the file to check, which may or may not exist. + # + proc isPgpSignatureFileName { fileName } { + if {[string length $fileName] == 0} then { + return false + } + + set extension [file extension $fileName] + + if {$extension eq ".asc"} then { + if {[file exists $fileName]} then { + return [::PackageRepository::isPgpSignature [readFile $fileName]] + } else { + return true + } + } else { + return false + } + } + + # + # NOTE: This procedure returns non-zero if the specified file seems to be + # a Harpy script certificate file. The fileName argument is the name + # of the file to check, which may or may not exist. + # + proc isHarpyCertificateFileName { fileName } { + if {[string length $fileName] == 0} then { + return false + } + + set extension [file extension $fileName] + + if {$extension eq ".harpy"} then { + if {[file exists $fileName]} then { + return [::PackageRepository::isHarpyCertificate [readFile $fileName]] + } else { + return true + } + } else { + return false + } + } # # NOTE: This procedure adds a directory to the auto-path of the specified # language (i.e. native Tcl or Eagle). The directory will not be # added if it is already present. The language argument must be the @@ -125,71 +191,122 @@ } else { error "unsupported language, no idea how to modify auto-path" } } + # + # NOTE: This procedure downloads a single file from the package file server, + # writing its contents to the specified local file name. It can also + # verify the PGP signatures. When a PGP signature file is + # downloaded, this procedure assumes the corresponding data file was + # already downloaded (i.e. since OpenPGP needs both to perform the + # signature checks). The language argument must be one of the + # literal strings "eagle", "tcl", or "client". The version argument + # must be one of the literal strings "8.4", "8.5", or "8.6" when the + # language is "tcl" -OR- the literal string "1.0" when the language + # is either "eagle" or "client". The fileName argument is a file + # name relative to the language and version-specific directory on the + # package file server. The localFileName argument is the file name + # where the downloaded file should be written. The usePgp argument + # should be non-zero when an OpenPGP signature file needs to be + # downloaded and verified for the downloaded file. + # + proc downloadOneFile { language version fileName localFileName usePgp } { + variable baseUri + variable downloadUri + variable quiet + + # + # NOTE: First, build the full relative file name to download from + # the remote package repository. + # + set fileName [file join $language $version $fileName] + set uri [subst $downloadUri] + + # + # NOTE: Then, in one step, download the file from the package file + # server and write it to the specified local file. + # + if {[isEagle]} then { + writeFile $localFileName [interp readorgetscriptfile -- "" $uri] + } else { + writeFile $localFileName \ + [::PackageRepository::getFileViaHttp $uri 10 stdout $quiet] + } + + # + # NOTE: Is use of OpenPGP for signature verification enabled? Also, + # did we just download an OpenPGP signature file? + # + if {$usePgp && [isPgpSignatureFileName $localFileName]} then { + # + # NOTE: Attempt to verify the OpenPGP signature. If this fails, + # an error is raised. + # + if {![::PackageRepository::verifyPgpSignature $localFileName]} then { + error [appendArgs \ + "bad PGP signature \"" $localFileName \"] + } + } + } + # # NOTE: This procedure attempts to download a list of files, optionally # persistening them for subsequent uses by the target language. - # The language argument must be the literal string "eagle" or the - # literal string "tcl". The version argument must be the literal - # string "8.4", "8.5", or "8.6" when the language is "tcl" -OR- - # the literal string "1.0" when the language is "eagle". The - # fileNames argument must be a well-formed list of file names to - # download, each one relative to the language/version-specific - # directory on the package file server. The persistent argument - # should be non-zero if the downloaded files should be saved to - # permanent storage for subsequent use. The usePgp argument - # should be non-zero when an OpenPGP signature file needs to be - # downloaded and verified for each downloaded file. The + # The language argument must be one of the literal strings "eagle", + # "tcl", or "client". The version argument must be one of the + # literal strings "8.4", "8.5", or "8.6" when the language is "tcl" + # -OR- the literal string "1.0" when the language is either "eagle" + # or "client". The fileNames argument must be a well-formed list + # of file names to download, each one relative to the language and + # version-specific directory on the package file server. The + # persistent argument should be non-zero if the downloaded files + # should be saved to permanent storage for subsequent use. The + # usePgp argument should be non-zero when an OpenPGP signature file + # needs to be downloaded and verified for each downloaded file. The # useAutoPath argument should be non-zero to modify the auto-path # to include the temporary or persistent directories containing # the downloaded files. # # proc downloadFiles { language version fileNames persistent usePgp useAutoPath } { - variable baseUri - variable downloadUri - variable persistentDirectory - variable quiet + variable clientDirectory + variable persistentRootDirectory + variable temporaryRootDirectory + + set client false if {[string length $language] == 0 || $language eq "eagle"} then { if {$version ne "1.0"} then { error "unsupported Eagle version" } } elseif {$language eq "tcl"} then { if {$version ne "8.4" && $version ne "8.5" && $version ne "8.6"} then { error "unsupported Tcl version" } + } elseif {$language eq "client"} then { + if {$version ne "1.0"} then { + error "unsupported client version" + } + + set client true } else { error "unsupported language" } - if {$persistent} then { - set downloadRootDirectory $persistentDirectory - } else { - set directoryNameOnly [appendArgs \ - pkgd_ [string trim [pid] -] _ [string trim [clock seconds] -]] - - global env - - if {[info exists env(PKGD_TEMP)]} then { - set downloadRootDirectory $env(PKGD_TEMP) - } elseif {[info exists env(TEMP)]} then { - set downloadRootDirectory $env(TEMP) - } elseif {[info exists env(TMP)]} then { - set downloadRootDirectory $env(TMP) - } else { - error "please set PKGD_TEMP (via environment) to temporary directory" - } - - set downloadRootDirectory [file join \ - $downloadRootDirectory $directoryNameOnly] - } - - set downloadDirectories [list] + set temporaryDirectory [file join $temporaryRootDirectory \ + [appendArgs pkgd_ [string trim [pid] -] _ [string trim \ + [clock seconds] -]]] + + if {$client} then { + set persistentDirectory $clientDirectory + } else { + set persistentDirectory $persistentRootDirectory + } + + set downloadedFileNames [list] foreach fileName $fileNames { if {[string length $fileName] == 0 || \ [file pathtype $fileName] ne "relative"} then { error [appendArgs \ @@ -201,61 +318,62 @@ if {[llength $directoryParts] == 0} then { error [appendArgs \ "bad file name \"" $fileName "\", no directory parts"] } - set downloadDirectory [file normalize [eval file join \ - [list $downloadRootDirectory] $directoryParts]] + set directory(temporary) [file normalize [eval \ + file join [list $temporaryDirectory] $directoryParts]] + + set directory(persistent) [file normalize [eval \ + file join [list $persistentDirectory] $directoryParts]] + + set fileNameOnly [file tail $fileName] set downloadFileName [file normalize [file join \ - $downloadDirectory [file tail $fileName]]] + $directory(temporary) $fileNameOnly]] - if {!$persistent} then { - catch {file delete $downloadFileName} + if {[file exists $downloadFileName]} then { + error [appendArgs \ + "temporary file name \"" $downloadFileName \ + "\" already exists"] } file mkdir [file dirname $downloadFileName] - - set savedFileName $fileName - set fileName [file join $language $version $fileName] - set uri [subst $downloadUri] - set fileName $savedFileName - - if {[isEagle]} then { - writeFile $downloadFileName \ - [interp readorgetscriptfile -- "" $uri] - } else { - writeFile $downloadFileName \ - [::PackageRepository::getFileViaHttp $uri 10 stdout $quiet] - } - - if {$usePgp} then { - set downloadSignatureFileName [appendArgs $downloadFileName .asc] - - set savedFileName $fileName - set fileName [file join \ - $language $version [appendArgs $fileName .asc]] - - set uri [subst $downloadUri] - set fileName $savedFileName - - if {[isEagle]} then { - writeFile $downloadSignatureFileName \ - [interp readorgetscriptfile -- "" $uri] - } else { - writeFile $downloadSignatureFileName \ - [::PackageRepository::getFileViaHttp $uri 10 stdout $quiet] - } - - if {![::PackageRepository::verifyPgpSignature \ - $downloadSignatureFileName]} then { - error [appendArgs \ - "bad PGP signature \"" $downloadSignatureFileName \"] - } - } - - lappend downloadDirectories $downloadDirectory + downloadOneFile $language $version $fileName $downloadFileName $usePgp + + lappend downloadedFileNames [list \ + $fileNameOnly $directory(temporary) $directory(persistent)] + + if {$usePgp && ![isPgpSignatureFileName $downloadFileName]} then { + downloadOneFile $language $version [appendArgs $fileName .asc] \ + [appendArgs $downloadFileName .asc] $usePgp + + lappend downloadedFileNames [list \ + [appendArgs $fileNameOnly .asc] $directory(temporary) \ + $directory(persistent)] + } + } + + set downloadDirectories [list] + + foreach downloadedFileName $downloadedFileNames { + set directory(temporary) [lindex $downloadedFileName 1] + + if {$persistent} then { + set fileNameOnly [lindex $downloadedFileName 0] + set directory(persistent) [lindex $downloadedFileName 2] + + file mkdir $directory(persistent) + + file copy -- \ + [file join $directory(temporary) $fileNameOnly] \ + [file join $directory(persistent) $fileNameOnly] + + lappend downloadDirectories $directory(persistent) + } else { + lappend downloadDirectories $directory(temporary) + } } set downloadDirectories [lsort -unique $downloadDirectories] if {$useAutoPath} then { @@ -279,13 +397,13 @@ ::PackageRepository::maybeReadSettingsFile [info script] # # NOTE: Setup the variables, within this namespace, used by this script. # - setupDownloadVars + setupDownloadVars [info script] # # NOTE: Provide the package to the interpreter. # package provide Eagle.Package.Downloader \ [expr {[isEagle] ? [info engine PatchLevel] : "1.0"}] } Index: client/1.0/pkgd.eagle.harpy ================================================================== --- client/1.0/pkgd.eagle.harpy +++ client/1.0/pkgd.eagle.harpy @@ -19,32 +19,32 @@ None Mistachkin Systems - 53a7ee35-d269-407b-9849-062af5a64876 + 2cf31dc5-d48b-44f1-afad-103b81c554ef SHA512 Script - 2016-08-19T02:47:10.2744609Z + 2016-08-19T19:13:42.5234844Z -1.00:00:00 0x2c322765603b5278 - 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 Index: client/1.0/pkgr.eagle ================================================================== --- client/1.0/pkgr.eagle +++ client/1.0/pkgr.eagle @@ -122,31 +122,42 @@ } else { return false } } + # + # NOTE: This procedure returns the fully qualified name of the directory + # where temporary files should be written. The envVarName argument + # is an optional extra environment variable to check (first). + # + proc getFileTempDirectory { {envVarName ""} } { + global env + + if {[string length $envVarName] > 0 && \ + [info exists env($envVarName)]} then { + return $env($envVarName) + } elseif {[info exists env(TEMP)]} then { + return $env(TEMP) + } elseif {[info exists env(TMP)]} then { + return $env(TMP) + } else { + error [appendArgs \ + "please set " $envVarName \ + " (via environment) to temporary directory"] + } + } + # # NOTE: This procedure returns a unique temporary file name. A script # error is raised if this task cannot be accomplished. There are # no arguments. # proc getFileTempName {} { if {[isEagle]} then { return [file tempname] } else { - global env - - if {[info exists env(PKGR_TEMP)]} then { - set directory $env(PKGD_TEMP) - } elseif {[info exists env(TEMP)]} then { - set directory $env(TEMP) - } elseif {[info exists env(TMP)]} then { - set directory $env(TMP) - } else { - error "please set PKGR_TEMP (via environment) to temporary directory" - } - + set directory [getFileTempDirectory PKGR_TEMP] set counter [expr {[pid] ^ int(rand() * 0xFFFF)}] while {1} { set fileNameOnly [format tcl%04X.tmp $counter] set fileName [file join $directory $fileNameOnly] Index: client/1.0/pkgr.eagle.harpy ================================================================== --- client/1.0/pkgr.eagle.harpy +++ client/1.0/pkgr.eagle.harpy @@ -19,32 +19,32 @@ None Mistachkin Systems - 88d42a28-1e95-4dd7-aaf9-11bb262f10d0 + c8d855d3-dc6c-4ff7-b559-6920bdc2c9ef SHA512 Script - 2016-08-19T02:47:51.4043437Z + 2016-08-19T19:13:22.5840312Z -1.00:00:00 0x2c322765603b5278 - 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