pax_global_header 0000666 0000000 0000000 00000000064 14764245604 0014526 g ustar 00root root 0000000 0000000 52 comment=3e3e401acea46a174831a9d9a72a1017595335da
deken-0.10.4/ 0000775 0000000 0000000 00000000000 14764245604 0012676 5 ustar 00root root 0000000 0000000 deken-0.10.4/LICENSE.txt 0000664 0000000 0000000 00000003064 14764245604 0014524 0 ustar 00root root 0000000 0000000 This software is copyrighted by IOhannes m zmölnig Chris McCormick and others.
The following terms (the "Standard Improved BSD License") apply to all files
associated with the software unless explicitly disclaimed in individual files:
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided
with the distribution.
3. The name of the author may not be used to endorse or promote
products derived from this software without specific prior
written permission.
THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR
BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
THE POSSIBILITY OF SUCH DAMAGE.
deken-0.10.4/Makefile 0000664 0000000 0000000 00000001036 14764245604 0014336 0 ustar 00root root 0000000 0000000 default:
@echo "make dek: build a legacy deken-package for the deken-plugin"
plugin_version := $(shell egrep "^if.*::deken::versioncheck" deken-plugin.tcl | sed -e 's| *].*||' -e 's|.* ||')
dek: deken-plugin-$(plugin_version)--externals.zip
.PHONY: dek deken-plugin
deken-plugin: deken-plugin.tcl README.plugin.txt LICENSE.txt README.deken.pd
rm -rf $@
mkdir -p $@
cp $^ $@
mv $@/README.plugin.txt $@/README.txt
deken-plugin-$(plugin_version)--externals.zip: deken-plugin
deken package --dekformat=0 --version=$(plugin_version) $^
deken-0.10.4/README.deken.pd 0000664 0000000 0000000 00000000567 14764245604 0015255 0 ustar 00root root 0000000 0000000 #N canvas 88 150 489 239 12;
#X text 49 42 deken: library package manager for Pd;
#X text 47 53 ======================================;
#X obj 49 175 cnv 15 400 30 empty empty empty 20 12 0 14 -233017 -66577
0;
#X text 56 179 you MUST RESTART Pd \, in order to use the new version.
;
#X text 47 77 version: v0.10.4;
#X text 47 139 thank you for installing the deken-plugin.;
deken-0.10.4/README.md 0000664 0000000 0000000 00000007035 14764245604 0014162 0 ustar 00root root 0000000 0000000 A minimal package management system for Pure Data externals.

Packages are stored on and can be installed using the `Help -> Find Packages` menu after installing the [GUI plugin](https://raw.githubusercontent.com/pure-data/deken/main/deken-plugin.tcl).
## README.1st ##
Since [`Pd-0.47`](http://puredata.info/downloads/pure-data/releases/0.47-0),
the `deken-plugin` is included into Pure Data itself,
so the only reason to manually install it is to get the newest version.
Main development of the plugin is still happening in *this* repository,
so you might want to manually install the plugin to help testing new features.
When manually installing the `deken-plugin`, Pd will use it if (and only if) it has a greater version number
than the one included in Pd.
In this case you will see something like the following in the Pd-console (you first have to raise the verbosity to `Debug`):
> `[deken]: installed version [0.2.1] < 0.2.3...overwriting!`
> `deken-plugin.tcl (Pd externals search) in /home/frobnozzel/.local/lib/pd/extra/deken-plugin/ loaded.`
## Download/Install ##
On any recent version of Pd (that already comes with deken included), you can
use `Help -> Find Packages` itself to search and install newer versions of the
plugin.
Just search for `deken-plugin` and install the latest and greatest release of the plugin.
For manual installation (e.g. because you want to test a developer version of the plugin),
click to download [deken-plugin.tcl](https://raw.githubusercontent.com/pure-data/deken/main/deken-plugin.tcl)
and save it to your Pd folder:
* Linux = `~/.local/lib/pd/extra/deken-plugin/` (with Pd<0.47 try `~/pd-externals/deken-plugin/`)
* OSX = `~/Library/Pd/deken-plugin/`
* Windows = `%AppData%\Pd\deken-plugin\`
Then select `Help -> Find Packages` and type the name of the external you would like to search for.
## Trusting packages
The `deken-plugin` will help you find and install Pd-libraries.
However, it does not verify whether a given package is downloaded from a trusted source or not.
As of now, the default package source is http://puredata.info.
Anybody who has an account on that website (currently that's a few thousand people) can upload packages,
that the `deken-plugin` will happily find and install for you.
In order to make these packages more trustworthy, we ask people to sign their uploaded packages with the GPG-key.
Unfortunately the deken-plugin does not check these signatures yet.
If you are concerned about the authenticity of a given download, you can check the GPG-signature manually,
by following these steps:
- Navigate to `Help -> Find Packages` and search for an external
- Right-Click one of the search results
- Select "Copy package URL" to copy the link to the downloadable file to your clipboard
- Download the package from the copied link
- Back in the deken search results, select "Copy OpenGPG signature URL"
- Download the GPG-signature from the copied link to the same location as the package
- Run `gpg --verify` on the downloaded file
If the signature is correct, you can decide yourself whether you actually trust the person who signed:
- Do you trust the signature to be owned by the person?
- Do you know the person?
- Do you trust them enough to let them install arbitrary software on your machine?
# Developers #
`deken` comes with a tool to package and upload your own library builds.
See [developer/README.md](./developer/README.md) for more information.
deken-0.10.4/README.plugin.txt 0000664 0000000 0000000 00000004517 14764245604 0015700 0 ustar 00root root 0000000 0000000 A minimal package management system for Pure Data externals.
============================================================
Packages are stored on and can be installed using the
`Help -> Find Packages` menu.
## README.1st ##
Since Pd-0.47, the `deken-plugin` is included into Pure Data itself,
so the only reason to manually install it is to get the newest version.
When manually installing the `deken-plugin`, Pd will use it if (and only if) it
has a greater version number than the one included in Pd.
In this case you will see something like the following in the Pd-console (you
first have to raise the verbosity to `Debug`):
> Loading plugin: /home/zmoelnig/src/puredata/deken/deken-plugin/deken-plugin.tcl
> [deken]: installed version [0.2.1] < 0.2.3...overwriting!
> [deken] deken-plugin.tcl (Pd externals search) loaded from /home/zmoelnig/src/puredata/deken/deken-plugin.
## Trusting packages
The `deken-plugin` will help you find and install Pd-libraries.
However, it does not verify whether a given package is downloaded from a trusted
source or not.
As of now, the default package source is https://deken.puredata.info/.
Anybody who has an account on the https://puredata.info website (currently
that's a few thousand people) can upload packages, that the `deken-plugin` will
happily find and install for you.
In order to make these packages more trustworthy, we ask people to sign their
uploaded packages with the GPG-key.
Unfortunately the deken-plugin does not check these signatures yet.
If you are concerned about the authenticity of a given download, you can check
the GPG-signature manually, by following these steps:
- Navigate to `Help -> Find Packages` and search for an external
- Right-Click one of the search results
- Select "Copy package URL" to copy the link to the downloadable file to your clipboard
- Download the package from the copied link
- Back in the deken search results, select "Copy OpenGPG signature URL"
- Download the GPG-signature from the copied link to the same location as the package
- Run `gpg --verify` on the downloaded file
If the signature is correct, you can decide yourself whether you actually trust
the person who signed:
- Do you trust the signature to be owned by the person?
- Do you know the person?
- Do you trust them enough to let them install arbitrary software on your machine?
deken-0.10.4/deken-extra-plugins/ 0000775 0000000 0000000 00000000000 14764245604 0016564 5 ustar 00root root 0000000 0000000 deken-0.10.4/deken-extra-plugins/deken-xtra-apt-helper.py 0000775 0000000 0000000 00000014136 14764245604 0023247 0 ustar 00root root 0000000 0000000 #!/usr/bin/env python3
import re
import fnmatch
import itertools
aptcache = None
class EmptyVersion:
"""helper class that provides an empty versions member"""
def __init__(self, versions=[]):
self.versions = versions
def parseArgs():
import argparse
parser = argparse.ArgumentParser()
p = parser.add_argument_group(
title="constraints",
)
p.add_argument(
"--os",
default="Linux",
choices=["Linux"],
help="target operating system [DEFAULT: %(default)r]",
)
p.add_argument(
"--architecture",
default=None,
help="target CPU architecture [DEFAULT: 'native']",
)
p.add_argument(
"--floatsize",
type=int,
default=32,
help="target floatsize [DEFAULT: %(default)s}]",
)
p = parser.add_argument_group(
title="API",
)
p.add_argument(
"--api",
type=int,
choices=[
0,
1,
],
required=True,
help="Output format version (0=test; 1=current)",
)
parser.add_argument(
"pkg",
nargs="*",
help="package(s) to search for",
)
args = parser.parse_args()
return args
def initializeAptCache():
import apt
global aptcache
aptcache = apt.Cache()
def stripSuffix(s, suffix):
if s.startswith(suffix):
return s[len(suffix) :]
return s
def hasDependency(versioned_pkg, dependencies: list) -> bool:
"""returns True iff (apt.package.Version) depends
on any package in (list[str]);
otherwise returns False"""
for d in versioned_pkg.dependencies:
for bd in d:
if bd.name in dependencies:
return True
return False
def getPackages(pkgs, arch=None, floatsize=32):
pd32deps = {"puredata-core", "puredata", "puredata-gui", "pd"}
pd64deps = {"puredata64-core", "puredata64", "puredata-gui", "pd64"}
if floatsize == 64:
pddeps = pd64deps
else:
pddeps = pd32deps
if pkgs == ["*"]:
pkgs = []
pkgmatch = re.compile(
"(pd-|pd64-)?(" + "|".join(fnmatch.translate(p) for p in pkgs) + ")"
).match
packages = {}
for p in aptcache:
# filter out known non-externals
if p.shortname.startswith("puredata"):
continue
# filter out unwanted architectures
if arch and p.architecture() != arch:
continue
for v in p.versions:
if pkgs:
names = [p.shortname] + v.provides
matches = [
matchedname
for name in {*names}
for matchedname in [pkgmatch(name)]
if matchedname
]
if not matches:
continue
else:
# all externals
matches = [p.shortname, None, None]
if p.shortname.startswith("pd-"):
matches[2] = p.shortname[3:]
elif p.shortname.startswith("pd64-"):
matches[2] = p.shortname[5:]
matches = [matches]
# we only take packages that depend on Pd
if not hasDependency(v, pddeps):
continue
for m in matches:
for n in {m[0], m[2]}:
npkgs = packages.get(n) or set()
npkgs.add(v)
packages[n] = npkgs
return itertools.chain(*packages.values())
def getOrigin(
versioned_pkg,
fallback_origin="apt",
fallback_date=None,
trusted="",
untrusted="?",
):
"""get the (one) package origin as a tuple of repository & archive,
e.g. ("Debian", "bookworm/main")
if the repository is trusted, the string is appended, otherwise the .
prefer trusted sources over untrusted.
"""
origins = [o for o in versioned_pkg.origins if o.trusted]
trust = trusted
if not origins:
origins = versioned_pkg.origins
trust = untrusted
if not origins:
return (fallback_origin, fallback_date, None)
origin = None
codename = None
component = None
for o in origins:
if all((origin, codename, component)):
break
try:
origin = origin or o.label or o.origin or fallback_uploader
except AttributeError:
pass
try:
codename = codename or o.codename or o.archive
except AttributeError:
pass
try:
component = component or o.component
except AttributeError:
pass
origin = origin or fallback_origin
if codename or component:
if not component:
codename_component = codename
else:
codename_component = f"{codename or '???'}/{component}"
else:
codename_component = fallback_date
return (origin, codename_component)
def showPackages(pkgs):
"""create a parseseable representation for each package"""
for p in sorted(set(pkgs)):
library = p.package.name
version = p.version
arch = p.architecture
uploader, date = getOrigin(p)
uri = p.uri
status = p.summary
installed = p.package.is_installed
state = "Already installed" if installed else "Provided"
comment = f"{state} by {uploader} ({date})"
print(
f"{library}\t{version}\t{arch}\t{int(installed)}\t{uploader}\t{date}\t{uri}\t{status}\t{comment}"
)
def main():
args = parseArgs()
if args.api not in {0, 1}:
return
if not args.api:
# quick sanity check if python-apt is available
import apt
return
initializeAptCache()
if not args.architecture:
for p in ["dpkg", "apt", "bash"]:
try:
args.architecture = aptcache.get(f"{p}:native").architecture()
break
except AttributeError:
pass
packages = getPackages(args.pkg, arch=args.architecture, floatsize=args.floatsize)
showPackages(packages)
if __name__ == "__main__":
main()
deken-0.10.4/deken-extra-plugins/deken-xtra-apt-plugin.tcl 0000664 0000000 0000000 00000023020 14764245604 0023405 0 ustar 00root root 0000000 0000000 # META NAME PdExternalsSearch
# META DESCRIPTION Search for externals Debian-packages via apt
# META AUTHOR IOhannes m zmölnig
# ex: set setl sw=2 sts=2 et
# Search URL:
# http://puredata.info/search_rss?SearchableText=xtrnl-
# The minimum version of TCL that allows the plugin to run
package require Tcl 8.4
## ####################################################################
## searching apt (if available)
namespace eval ::deken::apt {
namespace export search
namespace export install
variable distribution
variable pluginpath $::current_plugin_loadpath
}
proc ::deken::apt::search {name} {
if { [ info proc ${::deken::apt::searcher} ] } {
return [::deken::apt::search_pyapt ${name}]
}
return
}
proc ::deken::apt::search_pyapt {name} {
set result {}
set cmd "${::deken::apt::search_pyaptscript} --api 1 --os $::deken::platform(os) --architecture $::deken::platform(machine) --floatsize $::deken::platform(floatsize) -- ${name}"
set io [open "|${cmd}" ]
while { [gets ${io} line ] >= 0 } {
foreach {pkgname version arch is_installed uploader date uri status comment} [ split "${line}" "\t" ] {break}
set name ${pkgname}
set cmd [list ::deken::apt::install ${pkgname}=${version}]
set match 1
set contextcmds {}
if { ${uri} ne {} } {
lappend contextcmds [list [_ "Open package webpage" ] "pd_menucommands::menu_openfile [file dirname ${uri}]"]
lappend contextcmds [list [_ "Copy package URL" ] "clipboard clear; clipboard append ${uri}"]
if { ${is_installed} } {
lappend contextcmds {}
}
}
if { ${is_installed} } {
lappend contextcmds [::deken::apt::contextmenu::uninstall ${pkgname}]
}
set contextcmd [list ::deken::apt::contextmenu %W %x %y $contextcmds]
set norm [::deken::normalize_result "${pkgname} - ${status}" ${cmd} ${match} ${comment} ${status} ${contextcmd} ${pkgname} ${version} ${uploader} ${date}]
lappend result ${norm}
}
close ${io}
return ${result}
}
proc ::deken::apt::search_madison {name} {
set result []
if { [info exists ::deken::apt::distribution] } { } {
if { [ catch { exec lsb_release -si } ::deken::apt::distribution ] } {
set ::deken::apt::distribution {}
}
}
if { "${::deken::apt::distribution}" == "" } {
return
}
set name [ string tolower ${name} ]
array unset pkgs
array set pkgs {}
set _dpkg_query {dpkg-query -W -f ${db:Status-Abbrev}${Version}\n}
# pd-externals must depend on Pd somehow
# (this misses packages that provide pd-externals among *other* things,
# and therefore would only 'Recommend' Pd...)
set pdpkgs {pd puredata puredata-core puredata-gui}
if { $::deken::platform(floatsize) == 64 } {
set pdpkgs {pd64 puredata64 puredata64-core puredata-gui}
}
set pdfilter [concat {-F Depends -w} [join $pdpkgs " --or -F Depends -w "]]
set filter ${pdfilter}
if { "${name}" == "" } { } {
set filter "-F Package ${name} --and ( ${filter} )"
}
#set io [ open "|grep-aptavail -n -s Package,Version ${filter} | paste -sort -u | xargs apt-cache madison" r ]
set io [ open "|grep-aptavail -n -s Package ${filter} | sort -u | xargs apt-cache madison" r ]
while { [ gets ${io} line ] >= 0 } {
set llin [ split "${line}" "|" ]
set pkgname [ string trim [ lindex ${llin} 0 ] ]
#if { ${pkgname} ne ${searchname} } { continue }
set ver_ [ string trim [ lindex ${llin} 1 ] ]
set info_ [ string trim [ lindex ${llin} 2 ] ]
## status: is the package installed?
set state "Provided"
set installed 0
catch {
set io2cmd "|${_dpkg_query} ${pkgname} | grep -w -F \"ii ${ver_}\""
set io2 [ open "${io2cmd}" ]
if { [ gets ${io2} _ ] >= 0 } {
set state "Already installed"
set installed 1
} {
while { [ gets ${io2} _ ] >= 0 } { }
}
}
if { "Packages" eq [ lindex ${info_} end ] } {
set suite [ lindex ${info_} 1 ]
set arch [ lindex ${info_} 2 ]
if { ! [ info exists pkgs(${pkgname}/${ver_}) ] } {
set pkgs(${pkgname}/${ver_}) [ list ${pkgname} ${ver_} ${suite} ${arch} ${state} ${installed}]
}
}
}
foreach {name inf} [ array get pkgs ] {
set pkgname [ lindex ${inf} 0 ]
set v [ lindex ${inf} 1 ]
set suite [ lindex ${inf} 2 ]
set arch [ lindex ${inf} 3 ]
set state [ lindex ${inf} 4 ]
set cmd [list ::deken::apt::install ${pkgname}=${v}]
set match 1
set comment "${state} by ${::deken::apt::distribution} (${suite})"
set status "${pkgname}_${v}_${arch}.deb"
set contextcmd {}
set contextcmds {}
if { [ lindex ${inf} 5 ] } {
lappend contextcmds [::deken::apt::contextmenu::uninstall ${pkgname}]
}
if { ${contextcmds} eq {} } {
set contextcmd {}
} else {
set contextcmd [list ::deken::apt::contextmenu %W %x %y $contextcmds]
}
lappend result [list ${name} ${cmd} ${match} ${comment} ${status} ${pkgname} ${v} ${suite} ${contextcmd}]
}
# version-sort the results and normalize the result-string
set sortedresult []
if {[llength [info procs ::deken::normalize_result ]] > 0} {
foreach r [lsort -dictionary -decreasing -index 1 ${result} ] {
foreach {title cmd match comment status pkgname version suite cmd2} ${r} {break}
lappend sortedresult [::deken::normalize_result ${title} ${cmd} ${match} ${comment} ${status} ${cmd2} ${pkgname} ${version} Debian ${suite}]
}
} {
foreach r [lsort -dictionary -decreasing -index 1 ${result} ] {
# [list ${title} ${cmd} ${match} ${comment} ${status}]
foreach {title cmd match comment status} ${r} {break}
lappend sortedresult [list ${title} ${cmd} ${match} ${comment} ${status}]
}
}
return ${sortedresult}
}
proc ::deken::apt::contextmenu {widget theX theY commands} {
set m .dekenresults_contextMenu
destroy ${m}
if { ${commands} eq {} } { return }
menu ${m}
foreach lblcmd ${commands} {
if { ${lblcmd} eq {} } {
${m} add separator
} else {
foreach {lbl cmd} ${lblcmd} {break}
${m} add command -label ${lbl} -command ${cmd}
}
}
tk_popup ${m} [expr {[winfo rootx ${widget}] + ${theX}}] [expr {[winfo rooty ${widget}] + ${theY}}]
}
namespace eval ::deken::apt::contextmenu:: {}
proc ::deken::apt::contextmenu::uninstall {pkgname} {
return [list [format [_ "Uninstall '%s'" ] ${pkgname}] [list ::deken::apt::uninstall ${pkgname}]]
}
proc ::deken::apt::getsudo {} {
# for whatever reasons, we cannot have 'deken' as the description
# (it will always show ${prog} instead)
set desc deken::apt
if { [ catch { exec which pkexec } sudo ] } {
if { [ catch { exec which gksudo } sudo ] } {
set sudo ""
} {
set sudo "${sudo} -D ${desc} --"
}
}
if { ${sudo} == "" } {
::deken::post "Please install 'policykit-1', if you want to install system packages via deken..." error
}
return ${sudo}
}
proc ::deken::apt::install {pkg {version {}}} {
if { ${version} ne {} } {
set pkg "${pkg}=${version}"
}
set prog "apt-get install -y --show-progress ${pkg}"
set sudo [::deken::apt::getsudo]
if { ${sudo} ne "" } {
set cmdline "${sudo} ${prog}"
#::deken::post "${cmdline}" error
set io [ open "|${cmdline}" ]
while { [ gets ${io} line ] >= 0 } {
::deken::post "apt: ${line}"
}
if { [ catch { close ${io} } ret ] } {
::deken::post "apt::install failed to install ${pkg}" error
::deken::post "\tDid you provide the correct password and/or" error
::deken::post "\tis the apt database locked by another process?" error
}
}
}
proc ::deken::apt::uninstall {pkg} {
set prog "apt-get remove -y --show-progress ${pkg}"
set sudo [::deken::apt::getsudo]
if { ${sudo} ne "" } {
set cmdline "${sudo} ${prog}"
#::deken::post "${cmdline}" error
set io [ open "|${cmdline}" ]
while { [ gets ${io} line ] >= 0 } {
::deken::post "apt: ${line}"
}
if { [ catch { close ${io} } ret ] } {
::deken::post "apt::uninstall failed to remove ${pkg}" error
::deken::post "\tDid you provide the correct password and/or" error
::deken::post "\tis the apt database locked by another process?" error
}
}
}
proc ::deken::apt::register { } {
set pyfile [file join ${::deken::apt::pluginpath} deken-xtra-apt-helper.py]
if { [ catch {
exec ${pyfile} --api 0
set ::deken::apt::search_pyaptscript ${pyfile}
::deken::register ::deken::apt::search_pyapt
} ] } {
# failed to initialize python-apt backend
} else {
return 1
}
if { [ catch { exec apt-cache madison } _ ] } { } {
if { [ catch { exec which grep-aptavail } _ ] } { } {
if { [ catch {
::deken::register ::deken::apt::search_madison
} ] } {
#
} else {
return 1
}
}}
::pdwindow::debug "Not using unavailable APT-backend for deken\n"
return 0
}
proc ::deken::apt::initialize { } {
if { [::deken::apt::register] } {
::pdwindow::debug "Using APT as additional deken backend\n"
}
}
after idle ::deken::apt::initialize
deken-0.10.4/deken-plugin.tcl 0000664 0000000 0000000 00000415073 14764245604 0015776 0 ustar 00root root 0000000 0000000 # META NAME PdExternalsSearch
# META DESCRIPTION Search for externals zipfiles on puredata.info
# META AUTHOR chris@mccormick.cx
# META AUTHOR zmoelnig@iem.at
# ex: set setl sw=2 sts=2 et
# Search URL:
# http://deken.puredata.info/search?name=foobar
# TODOs
## + open embedded README
## - open README on homepage (aka "More info...")
## + remove library before unzipping
## + only show valid arch
## - only show most recent version (of each arch)
## - check whether the "cd" thing during unzip works on w32 and multiple drives
## - redirect ::deken::post to ::pdwindow::post (that is: use the results pane only for results)
## + make the "add to path" thingy configurable
# The minimum version of TCL that allows the plugin to run
package require Tcl 8.4 9
# If Tk or Ttk is needed
#package require Ttk
# Any elements of the Pd GUI that are required
# + require everything and all your script needs.
# If a requirement is missing,
# Pd will load, but the script will not.
package require http 2
# try enabling https if possible
if { [catch {package require tls} ] } {} else {
::tls::init -ssl2 false -ssl3 false -tls1 true
::http::register https 443 ::tls::socket
}
# try enabling PROXY support if possible
if { [catch {package require autoproxy} ] } {} else {
::autoproxy::init
if { ! [catch {package present tls} stdout] } {
::http::register https 443 ::autoproxy::tls_socket
}
}
package require pdwindow 0.1
package require pd_menucommands 0.1
package require pd_guiprefs
package require scrollboxwindow
package require scrollbox
namespace eval ::deken:: {
variable version
variable installpath
variable userplatform
variable hideforeignarch
variable hideoldversions
# results: {{title} {cmd} {description} {url} {ctxmenu}}
variable results
# selected: {library} {cmd} ...
variable selected {}
}
namespace eval ::deken::preferences {
variable installpath
variable installpath_x
variable userinstallpath
# automatically detected platform
variable platform
# user specified platform
variable userplatform
# boolean whether non-matching archs should be hidden
variable hideforeignarch
variable hideoldversions
}
namespace eval ::deken::utilities { }
## only register this plugin if there isn't any newer version already registered
## (if ::deken::version is defined and is higher than our own version)
proc ::deken::versioncheck {version} {
if { [info exists ::deken::version ] } {
set v0 [split ${::deken::version} "."]
set v1 [split ${version} "."]
foreach x ${v0} y ${v1} {
if { ${x} > ${y} } {
set msg [format [_ "\[deken\] installed version \[%1\$s\] > %2\$s...skipping!" ] ${::deken::version} ${version} ]
::pdwindow::debug "${msg}\n"
return 0
}
if { ${x} < ${y} } {
set msg [format [_ "\[deken\] installed version \[%1\$s\] < %2\$s...overwriting!" ] ${::deken::version} ${version} ]
::pdwindow::debug "${msg}\n"
set ::deken::version ${version}
return 1
}
}
set msg [format [_ "\[deken\] installed version \[%1\$s\] == %2\$s...skipping!" ] ${::deken::version} ${version} ]
::pdwindow::debug "${msg}\n"
return 0
}
set ::deken::version ${version}
return 1
}
## put the current version of this package here:
if { [::deken::versioncheck 0.10.4] } {
namespace eval ::deken:: {
namespace export open_searchui
variable winid .externals_searchui
variable resultsid ${winid}.results
variable infoid ${winid}.results
variable platform
variable architecture_substitutes
variable installpath
variable statustext
variable statustimer
variable backends
variable progressvar
variable progresstext
variable progresstimer
namespace export register
}
namespace eval ::deken::search:: { }
namespace eval ::deken::search::dekenserver { }
## FIXXXXME only initialize vars if not yet set
set ::deken::backends {}
set ::deken::installpath {}
set ::deken::userplatform {}
set ::deken::hideforeignarch false
set ::deken::hideoldversions false
set ::deken::show_readme 1
set ::deken::remove_on_install 1
set ::deken::add_to_path 0
set ::deken::keep_package 0
set ::deken::verify_sha256 1
set ::deken::searchtype name
set ::deken::statustimer {}
set ::deken::progresstimer {}
set ::deken::preferences::installpath {}
set ::deken::preferences::installpath_x {}
set ::deken::preferences::userinstallpath {}
set ::deken::preferences::platform {}
set ::deken::preferences::userplatform {}
set ::deken::preferences::hideforeignarch {}
set ::deken::preferences::hideoldversions {}
set ::deken::preferences::show_readme {}
set ::deken::preferences::remove_on_install {}
set ::deken::preferences::add_to_path {}
set ::deken::preferences::add_to_path_temp {}
set ::deken::preferences::keep_package {}
set ::deken::preferences::verify_sha256 {}
set ::deken::preferences::use_url_primary 1
set ::deken::preferences::use_urls_secondary 0
set ::deken::preferences::urls_secondary {}
set ::deken::preferences::use_urls_ephemeral 0
set ::deken::preferences::urls_ephemeral {}
set ::deken::platform(os) ${::tcl_platform(os)}
set ::deken::platform(machine) [string tolower ${::tcl_platform(machine)}]
set ::deken::platform(bits) [ expr {[ string length [ format %X -1 ] ] * 4} ]
set ::deken::platform(floatsize) 32
# architectures that can be substituted for each other
array set ::deken::architecture_substitutes {}
set ::deken::architecture_substitutes(x86_64) [list "amd64" ]
set ::deken::architecture_substitutes(amd64) [list "x86_64" ]
set ::deken::architecture_substitutes(i686) [list "i586" "i386"]
set ::deken::architecture_substitutes(i586) [list "i386"]
set ::deken::architecture_substitutes(armv6) [list "armv6l" "arm"]
set ::deken::architecture_substitutes(armv6l) [list "armv6" "arm"]
set ::deken::architecture_substitutes(armv7) [list "armv7l" "armv6l" "armv6" "arm"]
set ::deken::architecture_substitutes(armv7l) [list "armv7" "armv6l" "armv6" "arm"]
set ::deken::architecture_substitutes(powerpc) [list "ppc"]
set ::deken::architecture_substitutes(ppc) [list "powerpc"]
set ::deken::architecture_normalize(x86_64) "amd64"
set ::deken::architecture_normalize(i686) "i386"
set ::deken::architecture_normalize(i586) "i386"
set ::deken::architecture_normalize(i486) "i386"
set ::deken::architecture_normalize(armv6l) "armv6"
set ::deken::architecture_normalize(armv7l) "armv7"
set ::deken::architecture_normalize(powerpc) "ppc"
# normalize W32 OSs
if { [ string match "Windows *" "${::deken::platform(os)}" ] > 0 } {
# we are not interested in the w32 flavour, so we just use 'Windows' for all of them
set ::deken::platform(os) "Windows"
}
# normalize W32 CPUs
if { "Windows" eq "${::deken::platform(os)}" } {
# in redmond, intel only produces 32bit CPUs,...
if { "intel" eq "${::deken::platform(machine)}" } { set ::deken::platform(machine) "i686" }
# ... and all 64bit CPUs are manufactured by amd
#if { "amd64" eq "$::deken::platform(machine)" } { set ::deken::platform(machine) "x86_64" }
}
catch {
set ::deken::platform(machine) $::deken::architecture_normalize($::deken::platform(machine))
}
# ######################################################################
# ################ compatibility #######################################
# ######################################################################
# list-reverter (compat for tcl<8.5)
if {[info commands lreverse] == ""} {
proc lreverse list {
set res {}
set i [llength ${list}]
while {${i}} {
lappend res [lindex ${list} [incr i -1]]
}
set res
} ;# RS
}
# ######################################################################
# ################ utilities ##########################################
# ######################################################################
proc ::deken::utilities::setdefault {key value} {
upvar ${key} k
if { [info exists k] } {
set k
} else {
set k ${value}
}
}
proc ::deken::utilities::bool {value {fallback 0}} {
catch {set fallback [expr {bool(${value})} ] } stdout
return ${fallback}
}
proc ::deken::utilities::int {value {fallback 0}} {
catch {set fallback [expr {int(${value})} ] } stdout
return ${fallback}
}
proc ::deken::utilities::tristate {value {offset 0} {fallback 0} } {
catch {set fallback [expr {(int(${value}) + int(${offset}))% 3} ]} stdout
return ${fallback}
}
proc ::deken::utilities::list_unique {lst} {
array set cache {}
set result {}
foreach element ${lst} {
if { ! [info exists cache(${element})]} {
lappend result ${element}
}
set cache(${element}) 1
}
return ${result}
}
proc ::deken::utilities::lists_intersect {args} {
set numlists [llength ${args}]
if {${numlists} < 1} {return {}}
set cache("") 0
set elements {}
foreach lst ${args} {
set lst [::deken::utilities::list_unique ${lst}]
foreach element ${lst} {
if { ! [info exists cache(${element})]} {
lappend elements ${element}
}
incr cache(${element})
}
}
# ${elements} holds a list of unique elements (as they appeared)
# so filter out those that were not in all lists
set cache("") 0
set result {}
foreach element ${elements} {
if { ${numlists} == $cache(${element}) } {
lappend result ${element}
}
}
return ${result}
}
proc ::deken::utilities::expandpath {path} {
set map "@PD_PATH@"
lappend map ${::sys_libdir}
string map ${map} ${path}
}
proc ::deken::utilities::get_tmpfilename {{path ""} {ext ""} {prefix dekentmp}} {
for {set i 0} {true} {incr i} {
set tmpfile [file join ${path} ${prefix}.${i}${ext}]
if {![file exists ${tmpfile}]} {
return ${tmpfile}
}
}
}
proc ::deken::utilities::get_tmpdir {} {
proc _iswdir {d} { return [expr {[file isdirectory ${d}] * [file writable ${d}]}] }
set tmpdir ""
# TRASH_FOLDER: very old Macintosh. Mac OS X doesn't have this.
# TMPDIR: unices
# TMP, TEMP: windows
# TEPMDIR: for symmetry :-)
foreach {d} {TRASH_FOLDER TMPDIR TEMPDIR TEMP TMP} {
if { [info exists ::env(${d}) ] } {
set tmpdir $::env(${d})
if {[_iswdir ${tmpdir}]} {return ${tmpdir}}
}
}
set tmpdir "/tmp"
if {[_iswdir ${tmpdir}]} {return ${tmpdir}}
set tmpdir [pwd]
if {[_iswdir ${tmpdir}]} {return ${tmpdir}}
}
proc ::deken::utilities::is_writabledir {path} {
set fs [file separator]
set access [list RDWR CREAT EXCL TRUNC]
set tmpfile [::deken::utilities::get_tmpfilename ${path}]
# try creating tmpfile
if {![catch {open ${tmpfile} ${access}} channel]} {
close ${channel}
file delete ${tmpfile}
return true
}
return false
}
proc ::deken::utilities::get_writabledir {paths} {
foreach p ${paths} {
set xp [ ::deken::utilities::expandpath ${p} ]
if { [ ::deken::utilities::is_writabledir ${xp} ] } { return ${p} }
}
return
}
proc ::deken::utilities::rmrecursive {path} {
# recursively remove ${path} if it exists, traversing into each directory
# to delete single items (rather than just unlinking the parent directory)
set errors 0
set myname [lindex [info level 0] 0]
set children [glob -nocomplain -directory ${path} -types hidden *]
lappend children {*}[glob -nocomplain -directory ${path} *]
foreach child $children[set children {}] {
if {[file tail ${child}] in {. ..}} {
continue
}
if {[file isdirectory ${child}]} {
if {[file type ${child}] ne "link"} {
incr errors [${myname} ${child}]
}
}
if { [ catch { file delete -force ${child} } ] } {
incr errors
}
}
return ${errors}
}
# http://rosettacode.org/wiki/URL_decoding#Tcl
proc ::deken::utilities::urldecode {str} {
set specialMap {"[" "%5B" "]" "%5D"}
set seqRE {%([0-9a-fA-F]{2})}
set replacement {[format "%c" [scan "\1" "%2x"]]}
set modStr [regsub -all ${seqRE} [string map ${specialMap} ${str}] ${replacement}]
encoding convertfrom utf-8 [subst -nobackslashes -novariables ${modStr}]
}
proc ::deken::utilities::verbose {level message} {
::pdwindow::verbose ${level} "\[deken\] ${message}\n"
}
proc ::deken::utilities::debug {message} {
set winid ${::deken::winid}
if {[winfo exists ${winid}.tab.info]} {
::deken::post ${message} debug
} else {
::pdwindow::debug "\[deken\] ${message}\n"
}
}
if { [catch {package require tkdnd} ] } {
proc ::deken::utilities::dnd_init {windowid} { }
} else {
proc ::deken::utilities::dnd_init {windowid} {
::tkdnd::drop_target register ${windowid} DND_Files
bind ${windowid} <> {::deken::utilities::dnd_drop_files %D}
}
proc ::deken::utilities::dnd_drop_files {files} {
foreach f ${files} {
if { [regexp -all -nocase "\.(zip|dek|tgz|tar\.gz)$" ${f} ] } {
set msg [format [_ "installing deken package '%s'" ] ${f}]
::deken::statuspost ${msg}
::deken::install_package_from_file ${f}
} else {
set msg [format [_ "ignoring '%s': doesn't look like a deken package" ] ${f}]
::deken::statuspost ${msg}
}
}
return "link"
}
}
namespace eval ::deken::utilities::unzipper:: {
# we put a number of unzip methods in this namespace.
# they are tried in in alphabetical(!) order
# zipfs from Tcl>=8.7 (it seems this only works with Tcl>=9.0)
catch {
zipfs root
proc builtin_zipfs {zipfile path} {
if { [catch {
set base [file join [zipfs root] deken]
if { [catch {
package require Tcl 8
# yikes! Tcl8.7 uses 'zipfs mount '
zipfs mount ${base} ${zipfile}
} ] } {
# and Tcl9 uses 'zipfs mount '
zipfs mount ${zipfile} ${base}
}
foreach x [glob [file join ${base} *]] {
file copy -force -- ${x} ${path}
}
zipfs unmount ${base}
} stdout ] } {
::deken::utilities::debug "unzipper::zipfs: ${stdout}"
return 0
}
return 1
}
}
# ::zipfile::decode from tcllib
catch {
package require zipfile::decode
proc tcllib_zipfile {zipfile path} {
if { [catch {
::zipfile::decode::unzipfile "${zipfile}" "${path}"
::zipfile::decode::open "${zipfile}"
set zdict [::zipfile::decode::archive]
::zipfile::decode::close
foreach zfile [::zipfile::decode::files ${zdict}] {
set zfile [file join ${path} ${zfile}]
catch {
if { ! [file attributes ${zfile} -permissions] } {
if [file isdirectory ${zfile} ] {
set perms 0700
} else {
set perms 0600
}
file attributes ${zfile} -permissions ${perms}
}
}
}
} stdout ] } {
::deken::utilities::debug "unzipper::zipfile::decode: ${stdout}"
return 0
}
return 1
}
}
proc untar {zipfile path} {
if { [catch {
exec tar -xf "${zipfile}" -C "${path}"
} stdout ] } {
::deken::utilities::debug "unzipper::tar ${stdout}"
return 0
}
return 1
}
proc unzip {zipfile path} {
set result 0
if { [catch {
exec unzip -uo "${zipfile}" -d "${path}"
set result 1
} stdout ] } {
::deken::utilities::debug "unzipper::unzip ${stdout}"
}
return ${result}
}
if {$::tcl_platform(platform) eq "windows"} {
## VisualBasic is w32 only
proc windows_visualbasic {zipfile path} {
## create script-file
set vbsscript [::deken::utilities::get_tmpfilename [::deken::utilities::get_tmpdir] ".vbs" ]
set script {
On Error Resume Next
Set fso = CreateObject("Scripting.FileSystemObject")
'The location of the zip file.
ZipFile = fso.GetAbsolutePathName(WScript.Arguments.Item(0))
'The folder the contents should be extracted to.
ExtractTo = fso.GetAbsolutePathName(WScript.Arguments.Item(1))
'If the extraction location does not exist create it.
If NOT fso.FolderExists(ExtractTo) Then
fso.CreateFolder(ExtractTo)
End If
'Extract the contents of the zip file.
set objShell = CreateObject("Shell.Application")
set FilesInZip=objShell.NameSpace(ZipFile).items
objShell.NameSpace(ExtractTo).CopyHere(FilesInZip)
'In case of an error, exit
If Err.Number <> 0 Then
Err.Clear
WScript.Quit 1
End If
Set fso = Nothing
Set objShell = Nothing
}
if {![catch {set fileId [open ${vbsscript} "w"]}]} {
puts ${fileId} ${script}
close ${fileId}
}
if {![file exists ${vbsscript}]} {
## still no script, give up
return 0
}
## try to call the script
## (and windows requires the file to have a .zip extension!!!)
if { [ catch {
set zipfilezip ${zipfile}.zip
file rename ${zipfile} ${zipfilezip}
exec cscript "${vbsscript}" "${zipfilezip}" .
file rename ${zipfilezip} ${zipfile}
} stdout ] } {
catch { file rename ${zipfilezip} ${zipfile} }
catch { file delete "${vbsscript}" }
::deken::utilities::debug "unzipper::VBS-unzip(${vbsscript}): ${stdout}"
return 0
}
catch { file delete "${vbsscript}" }
return 1
}
}
}
proc ::deken::utilities::unzipper {zipfile {path .}} {
set unzippers [lsort -dictionary [info procs ::deken::utilities::unzipper::*]]
foreach unzip ${unzippers} {
set result 0
catch {
set result [ ${unzip} ${zipfile} ${path} ]
}
if {${result} ne 0} {
return 1
}
}
return 0
}
proc ::deken::utilities::extract {installdir filename fullpkgfile {keep_package 1}} {
if { ! [ file isdirectory "${installdir}" ] } {
return 0
}
::deken::statuspost [format [_ "Installing '%s'" ] ${filename} ] debug
set PWD [ pwd ]
cd ${installdir}
set success 1
if { [ string match *.dek ${fullpkgfile} ] } then {
if { ! [ ::deken::utilities::unzipper ${fullpkgfile} ${installdir} ] } {
if { [ catch { exec unzip -uo ${fullpkgfile} } stdout ] } {
::deken::utilities::debug "${stdout}"
set success 0
}
}
} elseif { [ string match *.zip ${fullpkgfile} ] } then {
if { ! [ ::deken::utilities::unzipper ${fullpkgfile} ${installdir} ] } {
if { [ catch { exec unzip -uo ${fullpkgfile} } stdout ] } {
::deken::utilities::debug "${stdout}"
set success 0
}
}
} elseif { [ string match *.tar.* ${fullpkgfile} ]
|| [ string match *.tgz ${fullpkgfile} ]
} then {
if { [ catch { exec tar xf ${fullpkgfile} } stdout ] } {
::deken::utilities::debug "${stdout}"
set success 0
}
}
cd ${PWD}
if { ${success} > 0 } {
::deken::post [format [_ "Successfully unzipped %1\$s into %2\$s."] ${filename} ${installdir} ] debug
if { ! "${keep_package}" } {
catch { file delete ${fullpkgfile} }
}
} else {
# Open both the fullpkgfile folder and the zipfile itself
# NOTE: in tcl 8.6 it should be possible to use the zlib interface to actually do the unzip
set msg [_ "Unable to extract package automatically." ]
::deken::post "${msg}" warn
::pdwindow::error "${msg}\n"
set msg ""
append msg [_ "Please perform the following steps manually:" ]
append msg "\n"
append msg [format [_ "1. Unzip %s." ] ${fullpkgfile} ]
append msg "\n"
if { [string match "*.dek" ${fullpkgfile}] } {
append msg " "
append msg [_ "You might need to change the file-extension from .dek to .zip" ]
append msg "\n"
}
append msg [format [_ "2. Copy the contents into %s." ] ${installdir}]
append msg "\n"
append msg [format [_ "3. Remove %s. (optional)" ] ${fullpkgfile} ]
append msg "\n"
::deken::post "${msg}"
pd_menucommands::menu_openfile ${fullpkgfile}
pd_menucommands::menu_openfile ${installdir}
}
return ${success}
}
proc ::deken::utilities::uninstall {path library} {
# recursively remove ${path}/${library} if it exists
set fullpath [file join ${path} ${library}]
if {[file exists ${fullpath}]} {
::deken::post [format [_ "Removing '%s'" ] ${fullpath} ] debug
if { [catch {
file delete -force "${fullpath}"
} stdout ] } {
set msg [format [_ "Uninstalling %1\$s from %2\$s failed!"] ${library} ${path}]
::deken::utilities::debug "${msg}\n ${stdout}"
return 0
}
}
return 1
}
namespace eval ::deken::utilities::sha256:: {
# we put a number of sha256sum methods in this namespace
# they are tried in alphabetical order
proc sha256sum {filename} {
set hash {}
catch { set hash [lindex [exec sha256sum ${filename}] 0] }
return ${hash}
}
proc shasum {filename} {
set hash {}
catch { set hash [lindex [exec shasum -a 256 ${filename}] 0] }
return ${hash}
}
if {$::tcl_platform(platform) eq "windows"} {
proc winpowershell {filename} {
set batscript [::deken::utilities::get_tmpfilename [::deken::utilities::get_tmpdir] ".bat" ]
set script {
@echo off
powershell -Command " & {Get-FileHash -Algorithm SHA256 -LiteralPath \"%1\" | Select-Object -ExpandProperty Hash}"
}
if {![catch {set fileId [open ${batscript} "w"]}]} {
puts ${fileId} ${script}
close ${fileId}
}
if {![file exists ${batscript}]} {
## still no script, give up
return ""
}
if { [ catch {
set hash [exec "${batscript}" "${filename}"]
} stdout ] } {
# ouch, couldn't run powershell script
::deken::utilities::verbose 1 "sha256.ps1 error: ${stdout}"
set hash ""
}
catch { file delete "${batscript}" }
return ${hash}
}
proc windows {filename} {
set hash {}
catch {
regexp {([a-fA-F0-9]{64})} [exec certUtil -hashfile ${filename} SHA256] hash
}
return ${hash}
}
}
catch {
package require sha256
proc ZZZtcllib {filename} {
# the tcllib implementation comes last, as it is really slow...
set hash {}
catch { set hash [::sha2::sha256 -hex -filename ${filename}] }
return ${hash}
}
}
}
proc ::deken::utilities::verify_sha256 {url pkgfile} {
set msg [format [_ "Skipping SHA256 verification of '%s'." ] ${url} ]
::deken::statuspost ${msg}
return -100
}
foreach impl [lsort -dictionary [info procs ::deken::utilities::sha256::*]] {
# skip any $impl that does not return a valid sha256 (or even throws an error)
if { [catch {
if { [${impl} ${::argv0}] eq "" } {
continue
}
}] } {
continue
}
# short name for the actually used interpreter
interp alias {} ::deken::utilities::sha256 {} ${impl}
proc ::deken::utilities::verify_sha256 {url pkgfile} {
::deken::statuspost [format [_ "SHA256 verification of '%s'" ] ${pkgfile} ] debug
::deken::syncgui
set retval 1
set isremote 1
set hashfile ""
# check if ${url} really is a local file
if { [file normalize ${url}] eq ${url} } {
# ${url} is really an absolute filename
# use it, if it exists
set hashfile "${url}.sha256"
set isremote 0
if { [file isfile ${hashfile} ] && [file readable ${hashfile}] } { } else {
set msg [format [_ "Unable to fetch reference SHA256 for '%s'." ] ${url} ]
::deken::utilities::verbose 0 ${msg}
::deken::statuspost ${msg} warn 0
return -10
}
} else {
# otherwise fetch it from the internet
if { [ catch {
set hashfile [::deken::utilities::download_file ${url}.sha256 [::deken::utilities::get_tmpfilename [::deken::utilities::get_tmpdir] ".sha256" ] ]
} stdout ] } {
::deken::utilities::verbose 0 "${stdout}"
# unable to download
set msg [format [_ "Unable to fetch reference SHA256 for '%s'." ] ${url} ]
::deken::utilities::verbose 0 ${msg}
::deken::statuspost ${msg} warn 0
return -10
}
}
if { "${hashfile}" eq "" } {
set retval -10
}
if { [ catch {
set fp [open ${hashfile} r]
set reference [string trim [string tolower [read ${fp}] ] ]
close ${fp}
if { ${isremote} } {
catch { file delete ${hashfile} }
}
# get hash of file
set hash [::deken::utilities::sha256 ${pkgfile} ]
set hash [string trim [string tolower ${hash} ] ]
if { ${hash} == "" } {
set msg [format [_ "Skipping SHA256 verification of '%s'." ] ${url} ]
::deken::statuspost ${msg}
return -100
}
# check if hash is sane
if { [string length ${hash}] != 64 || ! [string is xdigit ${hash}] } {
::deken::statuspost [format [_ "File checksum looks invalid: '%s'." ] ${hash}] warn 0
}
# check if reference is sane
if { [string length ${reference}] != 64 || ! [string is xdigit ${reference}] } {
# this is more grave than the sanity check for the file hash
# (since for the file hash we depend on the user's machine being able to
# produce a proper SHA256 hash)
::deken::statuspost [format [_ "Reference checksum looks invalid: '%s'." ] ${reference}] error 0
}
if { [string first ${reference} ${hash}] >= 0 } {
set retval 1
} else {
# SHA256 verification failed...
set retval 0
}
} stdout ] } {
::deken::utilities::verbose 0 "${stdout}"
# unable to verify
set msg [format [_ "Unable to perform SHA256 verification for '%s'." ] ${url} ]
::deken::utilities::verbose 0 ${msg}
::deken::statuspost ${msg} warn 0
set retval -20
}
return ${retval}
}
# it seems we found a working sha256 implementation, don't try the other ones...
break
}
proc ::deken::utilities::httpuseragent {} {
set httpagent [::http::config -useragent]
set pdversion "Pd/${::PD_MAJOR_VERSION}.${::PD_MINOR_VERSION}.${::PD_BUGFIX_VERSION}${::PD_TEST_VERSION}"
set platformstring [::deken::platform2string]
set tclversion "Tcl/[info patchlevel]"
::http::config -useragent "Deken/${::deken::version} (${platformstring}) ${pdversion} ${tclversion}"
return ${httpagent}
}
# download a file to a location
# http://wiki.tcl.tk/15303
proc ::deken::utilities::download_file {url outputfilename {progressproc {}}} {
set URL [string map {{[} "%5b" {]} "%5d"} ${url}]
set downloadfilename [::deken::utilities::get_tmpfilename [file dirname ${outputfilename}] ]
set f [open ${downloadfilename} w]
fconfigure ${f} -translation binary
set httpagent [::deken::utilities::httpuseragent]
if { [catch {
if { ${progressproc} eq {} } {
set httpresult [::http::geturl ${URL} -binary true -channel ${f}]
} else {
set httpresult [::http::geturl ${URL} -binary true -progress ${progressproc} -channel ${f}]
}
set ncode [::http::ncode ${httpresult}]
if {${ncode} != 200} {
## FIXXME: we probably should handle redirects correctly (following them...)
set err [::http::code ${httpresult}]
set msg [format [_ "Unable to download from %1\$s \[%2\$s\]" ] ${url} ${err} ]
::deken::post "${msg}" debug
set outputfilename ""
}
::http::cleanup ${httpresult}
} stdout ] } {
set msg [format [_ "Unable to download from '%s'!" ] ${url} ]
tk_messageBox \
-title [_ "Download failed" ] \
-message "${msg}\n${stdout}" \
-icon error -type ok \
-parent ${::deken::winid}
set outputfilename ""
}
::http::config -useragent ${httpagent}
flush ${f}
close ${f}
if { "${outputfilename}" != "" } {
catch { file delete ${outputfilename} }
if {[file exists ${outputfilename}]} {
::deken::utilities::debug [format [_ "Unable to remove stray file '%s'" ] ${outputfilename} ]
set outputfilename ""
}
}
if { ${outputfilename} != "" && "${outputfilename}" != "${downloadfilename}" } {
if {[catch { file rename ${downloadfilename} ${outputfilename}}]} {
::deken::utilities::debug [format [_ "Unable to rename downloaded file to '%s'" ] ${outputfilename} ]
set outputfilename ""
}
}
if { "${outputfilename}" eq "" } {
file delete ${downloadfilename}
}
return ${outputfilename}
}
# parse a deken-packagefilename into it's components:
# v0:: [-v-]?{()}-externals.
# v1:: [\[)}
# return: list [list ...]
proc ::deken::utilities::parse_filename {filename} {
set pkgname ${filename}
set archs [list]
set version ""
if { [ string match "*.dek" ${filename} ] } {
## deken filename v1: [v]()().dek
set archstring ""
if { ! [regexp {^([^\[\]\(\)]+)((\[[^\[\]\(\)]+\])*)((\([^\[\]\(\)]+\))*).*\.dek$} ${filename} _ pkgname optionstring _ archstring] } {
# oops, somewhat unparseable (e.g. a copy)
} else {
foreach {o _} [lreplace [split ${optionstring} {[]}] 0 0] {
if {![string first v ${o}]} {
set version [string range ${o} 1 end]
} else { # ignoring unknown option...
return [list {} {} {}]
}
}
foreach {a _} [lreplace [split ${archstring} "()"] 0 0] { lappend archs ${a} }
}
} elseif { [ regexp {(.*)-externals\..*} ${filename} _ basename] } {
## deken filename v0
set pkgname ${basename}
# basename [-v-]?{()}
## strip off the archs
set baselist [split ${basename} () ]
# get pkgname + version
set pkgver [lindex ${baselist} 0]
if { ! [ regexp "(.*)-v(.*)-" ${pkgver} _ pkgname version ] } {
set pkgname ${pkgver}
set version ""
}
# get archs
foreach {a _} [lreplace ${baselist} 0 0] {
# in filename.v0 the semantics of the last arch field ("bits") was unclear
# since this format predates float64 builds, we just force it to 32
regsub -- {-[0-9]+$} ${a} {-32} a
lappend archs ${a}
}
if { "x${archs}${version}" == "x" } {
# try again as -v
if { ! [ regexp "(.*)-v(.*)" ${pkgver} _ pkgname version ] } {
set pkgname ${pkgver}
set version ""
}
}
}
return [list ${pkgname} ${version} ${archs}]
}
# split filename extension from deken-packagefilename
proc ::deken::utilities::get_filenameextension {filename} {
if { [ regexp {.*(\.tar\.[^.]*)$} ${filename} _ ext ] } {
return ${ext}
}
return [file extension ${filename}]
}
# ######################################################################
# ################ preferences #########################################
# ######################################################################
proc ::deken::preferences::create_sources_entry {toplevel} {
# 3 panels
# - [ ] primary server
# - [ ] secondary URLs (list editable)
# (all URLs are searched if checked)
# - [ ] ephemeral URLs (list multiselectable)
# (if checked, and URLs are selected, only these are searched)
# (if checked, and no URLs are selected, all are searched)
set frame [::deken::preferences::newwidget ${toplevel}.servers]
labelframe ${frame} -text [_ "Search URLs:" ] -padx 5 -pady 5 -borderwidth 1
set numframes 0
if {1} {
set f [::deken::preferences::newwidget ${frame}.primary]
# primary URL
labelframe ${f} -borderwidth 0
checkbutton ${f}.use -text "${::deken::search::dekenserver::url_primary}" \
-variable ::deken::preferences::use_url_primary
${f} configure -labelwidget ${f}.use
pack ${f} -anchor "w" -fill both -expand 1 -side top
pack ${f}.use -anchor "w" -side left
incr numframes
}
if {0} {
set f [::deken::preferences::newwidget ${frame}.secondary]
# secondary URLs
labelframe ${f} -borderwidth 0
checkbutton ${f}.use \
-variable ::deken::preferences::use_urls_secondary
button ${f}.open \
-command [list ::deken::preferences::urls2_frame_create ${toplevel}] \
-text [_ "Additional search URLs" ]
${f} configure -labelwidget ${f}.use
pack ${f} -anchor "w" -fill both -expand 1 -side left
pack ${f}.use -anchor "w" -side left
pack ${f}.open -anchor "w" -side left
incr numframes
}
if {0} {
set f [::deken::preferences::newwidget ${frame}.ephemeral]
# ephemeral URLs
labelframe ${f} -borderwidth 0
checkbutton ${f}.use \
-variable ::deken::preferences::use_urls_ephemeral
button ${f}.open -text [_ "Ephemeral search URLs" ]
${f} configure -labelwidget ${f}.use
pack ${f} -anchor "w" -fill both -expand 1 -side left
pack ${f}.use -anchor "w" -side left
pack ${f}.open -anchor "w" -side left
incr numframes
}
if { ${numframes} > 1 } {
return ${frame}
}
return ""
}
proc ::deken::preferences::newwidget {basename} {
# calculate a widget name that has not yet been taken
set i 0
while {[winfo exists ${basename}${i}]} {incr i}
return ${basename}${i}
}
proc ::deken::preferences::urls2_frame_create {toplevel} {
set title [_ "Additional search URLs"]
set win ${toplevel}.urls2
destroy $win
toplevel $win
wm title $win "deken: ${title}"
::scrollbox::make ${win} ${::deken::preferences::urls_secondary} {} {}
#::deken::preferences::make_applybutton_frame ${win} \
# [list ::deken::preferences::urls2_frame_apply ${win}]
set cmd [list ::scrollboxwindow::apply ${win} ::deken::preferences::set_urls_secondary]
::deken::preferences::make_applybutton_frame ${win} \
"${cmd}; destroy ${win}"
bind ${win} [list after idle [list ::deken::preferences::cancel $win]]
}
proc ::deken::preferences::set_urls_secondary {urls} {
set ::deken::preferences::urls_secondary ${urls}
}
proc ::deken::preferences::create_pathpad {toplevel row {padx 2} {pady 2}} {
set pad [::deken::preferences::newwidget ${toplevel}.pad]
frame ${pad} -relief groove -borderwidth 2 -width 2 -height 2
grid ${pad} -sticky ew -row ${row} -column 0 -columnspan 3 -padx ${padx} -pady ${pady}
}
proc ::deken::preferences::create_packpad {toplevel {padx 2} {pady 2} } {
set mypad [::deken::preferences::newwidget ${toplevel}.pad]
frame ${mypad}
pack ${mypad} -padx ${padx} -pady ${pady} -expand 1 -fill "y"
return ${mypad}
}
proc ::deken::preferences::userpath_doit { winid } {
set installdir [::deken::do_prompt_installdir ${::deken::preferences::userinstallpath} ${winid}]
if { "${installdir}" != "" } {
set ::deken::preferences::userinstallpath "${installdir}"
}
}
proc ::deken::preferences::path_doit {rdb ckb path {mkdir true}} {
# handler for the check/create button
# if the path does not exist, disable the radiobutton and suggest to Create it
# if the path exists, check whether it is writable
# if it is writable, enable the radiobutton and disable the check/create button
# if it is not writable, keep the radiobutton disabled and suggest to (Re)Check
${ckb} configure -state normal
${rdb} configure -state disabled
if { ! [file exists ${path}] } {
${ckb} configure -text [_ "Create"]
if { ${mkdir} } {
catch { file mkdir ${path} }
}
}
if { [file exists ${path}] } {
${ckb} configure -text [_ "Check"]
}
if { [::deken::utilities::is_writabledir ${path} ] } {
${ckb} configure -state disabled
${rdb} configure -state normal
}
}
proc ::deken::preferences::update_displaypath {var value} {
set ${var} ${value}
}
proc ::deken::preferences::create_pathentry {toplevel row var path {generic false}} {
# only add absolute paths to the pathentries
set xpath [ ::deken::utilities::expandpath ${path} ]
if {! ${generic}} {
if { [file pathtype ${xpath}] != "absolute"} { return }
}
set rdb [::deken::preferences::newwidget ${toplevel}.path]
set chk [::deken::preferences::newwidget ${toplevel}.doit]
set pad [::deken::preferences::newwidget ${toplevel}.pad]
radiobutton ${rdb} -value ${path} -text "${path}" -variable ${var} \
-command [list ::deken::preferences::update_displaypath ${var}_x ${path}]
frame ${pad}
button ${chk} -text [_ "Browse" ] -command "::deken::preferences::path_doit ${rdb} ${chk} ${xpath}"
grid ${rdb} -sticky "w" -row ${row} -column 2
grid ${pad} -sticky "" -row ${row} -column 1 -padx 10
grid ${chk} -sticky nsew -row ${row} -column 0
if {! ${generic}} {
::deken::preferences::path_doit ${rdb} ${chk} ${xpath} false
}
return [list ${rdb} ${chk}]
}
proc ::deken::preferences::fill_frame {winid} {
::deken::preferences::create ${winid}
}
proc ::deken::preferences::create_pathframe {cnv winid} {
canvas ${cnv}.cnv \
-confine true
scrollbar ${cnv}.scrollv \
-command "${cnv}.cnv yview"
scrollbar ${cnv}.scrollh \
-orient horizontal \
-command "${cnv}.cnv xview"
${cnv}.cnv configure \
-xscrollincrement 0 \
-xscrollcommand " ${cnv}.scrollh set"
${cnv}.cnv configure \
-yscrollincrement 0 \
-yscrollcommand " ${cnv}.scrollv set" \
pack ${cnv}.cnv -side left -fill both -expand 1
pack ${cnv}.scrollv -side right -fill "y"
pack ${cnv}.scrollh -side bottom -fill "x" -before ${cnv}.cnv
set pathsframe [frame ${cnv}.cnv.f]
set row 0
### dekenpath: directory-chooser
# FIXME: should we ask user to add chosen directory to PATH?
set pathdoit [::deken::preferences::create_pathentry ${pathsframe} ${row} ::deken::preferences::installpath "USER" true]
incr row
[lindex ${pathdoit} 0] configure \
-foreground blue \
-value "USER" \
-textvariable ::deken::preferences::userinstallpath \
-variable ::deken::preferences::installpath \
-command {::deken::preferences::update_displaypath ::deken::preferences::installpath_x ${::deken::preferences::userinstallpath}}
[lindex ${pathdoit} 1] configure \
-text [_ "Browse" ] \
-command "::deken::preferences::userpath_doit ${winid}"
### dekenpath: default directories
if {[namespace exists ::pd_docsdir] && [::pd_docsdir::externals_path_is_valid]} {
set xpath [::pd_docsdir::get_externals_path]
if { [llength ${xpath}] } {
::deken::preferences::create_pathpad ${pathsframe} ${row}
incr row
}
foreach p ${xpath} {
::deken::preferences::create_pathentry ${pathsframe} ${row} ::deken::preferences::installpath ${p}
incr row
}
}
if { [llength ${::sys_staticpath}] } {
::deken::preferences::create_pathpad ${pathsframe} ${row}
incr row
}
set extradir [file join ${::sys_libdir} extra ]
foreach p ${::sys_staticpath} {
if { [file normalize ${p}] == ${extradir} } {
set p [file join @PD_PATH@ extra]
}
::deken::preferences::create_pathentry ${pathsframe} ${row} ::deken::preferences::installpath ${p}
incr row
}
if {[llength ${::sys_searchpath}]} {
::deken::preferences::create_pathpad ${pathsframe} ${row}
incr row
}
foreach p ${::sys_searchpath} {
::deken::preferences::create_pathentry ${pathsframe} ${row} ::deken::preferences::installpath ${p}
incr row
}
pack ${pathsframe} -fill "x"
${cnv}.cnv create window 0 0 -anchor "nw" -window ${pathsframe}
}
proc ::deken::preferences::create_pathwindow {parentwin} {
set winid ${parentwin}.pathwindow
if {[winfo exists ${winid}]} {
wm deiconify ${winid}
raise ${winid}
} else {
toplevel ${winid} -class DialogWindow
bind ${winid} {after idle {::deken::preferences::cancel %W}}
wm title ${winid} [_ "Deken Installation Target"]
frame ${winid}.frame
pack ${winid}.frame -side top -padx 6 -pady 3 -fill both -expand true
::deken::preferences::create_pathframe ${winid}.frame ${parentwin}
frame ${winid}.bframe
pack ${winid}.bframe -side bottom -fill "x" -pady 2m
button ${winid}.bframe.close -text [_ "Close" ] \
-command [list ::deken::preferences::cancel ${winid}]
pack ${winid}.bframe.close -side right -expand 1 -fill "x" -padx 15 -ipadx 10
}
}
proc ::deken::preferences::create {winid} {
# urgh...we want to know when the window gets drawn,
# so we can query the size of the pathentries canvas
# in order to get the scrolling-region right!!!
# this seems to be so wrong...
bind ${winid}