Index: datashare.scm
==================================================================
--- datashare.scm
+++ datashare.scm
@@ -14,812 +14,811 @@
;; GNU General Public License for more details.
;;
;; You should have received a copy of the GNU General Public License
;; along with Megatest. If not, see .
-(use ssax)
-(use sxml-serializer)
-(use sxml-modifications)
-(use regex)
-(use srfi-69)
-(use regex-case)
-(use posix)
-(use json)
-(use csv)
-(use srfi-18)
-(use format)
-
-(require-library iup)
-(import (prefix iup iup:))
-(require-library ini-file)
-(import (prefix ini-file ini:))
-
-(use canvas-draw)
-(import canvas-draw-iup)
-
-(use sqlite3 srfi-1 posix regex regex-case srfi-69)
-(import (prefix sqlite3 sqlite3:))
-
-(declare (uses configf))
-(declare (uses tree))
-(declare (uses margs))
-;; (declare (uses dcommon))
-;; (declare (uses launch))
-;; (declare (uses gutils))
-;; (declare (uses db))
-;; (declare (uses synchash))
-;; (declare (uses server))
-;; (declare (uses megatest-version))
-;; (declare (uses tbd))
-
-(include "megatest-fossil-hash.scm")
-
-;;
-;; GLOBALS
-;;
-(define *datashare:current-tab-number* 0)
-(define *args-hash* (make-hash-table))
-(define datashare:help (conc "Usage: datashare [action [params ...]]
-
-Note: run datashare without parameters to start the gui.
-
- list-areas : List the allowed areas
-
- list-versions : List versions available in
- options : -full, -vpatt patt
-
- publish : Publish data for area and with version
-
- get : Get a link to data, put the link in destpath
- options : -i iteration
-
- update : Update the link to data to the latest iteration.
-
-Part of the Megatest tool suite.
-Learn more at http://www.kiatoa.com/fossils/megatest
-
-Version: " megatest-fossil-hash)) ;; "
-
-;;======================================================================
-;; RECORDS
-;;======================================================================
-
-;; make-vector-record "testing" datastore pkg id area version_name store_type copied source_path iteration submitter datetime storegrp datavol quality disk_id comment
-;; testing
-(define (make-datashare:pkg)(make-vector 15))
-(define-inline (datashare:pkg-get-id vec) (vector-ref vec 0))
-(define-inline (datashare:pkg-get-area vec) (vector-ref vec 1))
-(define-inline (datashare:pkg-get-version_name vec) (vector-ref vec 2))
-(define-inline (datashare:pkg-get-store_type vec) (vector-ref vec 3))
-(define-inline (datashare:pkg-get-copied vec) (vector-ref vec 4))
-(define-inline (datashare:pkg-get-source_path vec) (vector-ref vec 5))
-(define-inline (datashare:pkg-get-iteration vec) (vector-ref vec 6))
-(define-inline (datashare:pkg-get-submitter vec) (vector-ref vec 7))
-(define-inline (datashare:pkg-get-datetime vec) (vector-ref vec 8))
-(define-inline (datashare:pkg-get-storegrp vec) (vector-ref vec 9))
-(define-inline (datashare:pkg-get-datavol vec) (vector-ref vec 10))
-(define-inline (datashare:pkg-get-quality vec) (vector-ref vec 11))
-(define-inline (datashare:pkg-get-disk_id vec) (vector-ref vec 12))
-(define-inline (datashare:pkg-get-comment vec) (vector-ref vec 13))
-(define-inline (datashare:pkg-get-stored_path vec) (vector-ref vec 14))
-(define-inline (datashare:pkg-set-id! vec val)(vector-set! vec 0 val))
-(define-inline (datashare:pkg-set-area! vec val)(vector-set! vec 1 val))
-(define-inline (datashare:pkg-set-version_name! vec val)(vector-set! vec 2 val))
-(define-inline (datashare:pkg-set-store_type! vec val)(vector-set! vec 3 val))
-(define-inline (datashare:pkg-set-copied! vec val)(vector-set! vec 4 val))
-(define-inline (datashare:pkg-set-source_path! vec val)(vector-set! vec 5 val))
-(define-inline (datashare:pkg-set-iteration! vec val)(vector-set! vec 6 val))
-(define-inline (datashare:pkg-set-submitter! vec val)(vector-set! vec 7 val))
-(define-inline (datashare:pkg-set-datetime! vec val)(vector-set! vec 8 val))
-(define-inline (datashare:pkg-set-storegrp! vec val)(vector-set! vec 9 val))
-(define-inline (datashare:pkg-set-datavol! vec val)(vector-set! vec 10 val))
-(define-inline (datashare:pkg-set-quality! vec val)(vector-set! vec 11 val))
-(define-inline (datashare:pkg-set-disk_id! vec val)(vector-set! vec 12 val))
-(define-inline (datashare:pkg-set-comment! vec val)(vector-set! vec 13 val))
-(define-inline (datashare:pkg-set-stored_path! vec val)(vector-set! vec 14 val))
-
-;;======================================================================
-;; DB
-;;======================================================================
-
-(define (datashare:initialize-db db)
- (for-each
- (lambda (qry)
- (sqlite3:execute db qry))
- (list
- "CREATE TABLE pkgs
- (id INTEGER PRIMARY KEY,
- area TEXT,
- version_name TEXT,
- store_type TEXT DEFAULT 'copy',
- copied INTEGER DEFAULT 0,
- source_path TEXT,
- stored_path TEXT,
- iteration INTEGER DEFAULT 0,
- submitter TEXT,
- datetime TIMESTAMP DEFAULT (strftime('%s','now')),
- storegrp TEXT,
- datavol INTEGER,
- quality TEXT,
- disk_id INTEGER,
- comment TEXT);"
- "CREATE TABLE refs
- (id INTEGER PRIMARY KEY,
- pkg_id INTEGER,
- destlink TEXT);"
- "CREATE TABLE disks
- (id INTEGER PRIMARY KEY,
- storegrp TEXT,
- path TEXT);")))
-
-(define (datashare:register-data db area version-name store-type submitter quality source-path comment)
- (let ((iter-qry (sqlite3:prepare db "SELECT max(iteration) FROM pkgs WHERE area=? AND version_name=?;"))
- (next-iteration 0))
- (sqlite3:with-transaction
- db
- (lambda ()
- (sqlite3:for-each-row
- (lambda (iteration)
- (if (and (number? iteration)
- (>= iteration next-iteration))
- (set! next-iteration (+ iteration 1))))
- iter-qry area version-name)
- ;; now store the data
- (sqlite3:execute db "INSERT INTO pkgs (area,version_name,iteration,store_type,submitter,source_path,quality,comment)
- VALUES (?,?,?,?,?,?,?,?);"
- area version-name next-iteration (conc store-type) submitter source-path quality comment)))
- (sqlite3:finalize! iter-qry)
- next-iteration))
-
-(define (datashare:get-id db area version-name iteration)
- (let ((res #f))
- (sqlite3:for-each-row
- (lambda (id)
- (set! res id))
- db
- "SELECT id FROM pkgs WHERE area=? AND version_name=? AND iteration=?;"
- area version-name iteration)
- res))
-
-(define (datashare:set-stored-path db id path)
- (sqlite3:execute db "UPDATE pkgs SET stored_path=? WHERE id=?;" path id))
-
-(define (datashare:set-copied db id value)
- (sqlite3:execute db "UPDATE pkgs SET copied=? WHERE id=?;" value id))
-
-(define (datashare:get-pkg-record db area version-name iteration)
- (let ((res #f))
- (sqlite3:for-each-row
- (lambda (a . b)
- (set! res (apply vector a b)))
- db
- "SELECT * FROM pkgs WHERE area=? AND version_name=? AND iteration=?;"
- area
- version-name
- iteration)
- res))
-
-;; take version-name iteration and register or update "lastest/0"
-;;
-(define (datashare:set-latest db id area version-name iteration)
- (let* ((rec (datashare:get-pkg-record db area version-name iteration))
- (latest-id (datashare:get-id db area "latest" 0))
- (stored-path (datashare:pkg-get-stored_path rec)))
- (if latest-id ;; have a record - bump the link pointer
- (datashare:set-stored-path db latest-id stored-path)
- (datashare:register-data db area "latest" 'link "auto" "na" stored-path "latest data"))))
-
-;; set a package ref, this is the location where the link back to the stored data
-;; is put.
-;;
-;; if there is nothing at that location then the record can be removed
-;; if there are no refs for a particular pkg-id then that pkg-id is a
-;; candidate for removal
-;;
-(define (datashare:record-pkg-ref db pkg-id dest-link)
- (sqlite3:execute db "INSERT INTO refs (pkg_id,destlink) VALUES (?,?);" pkg-id dest-link))
-
-(define (datashare:count-refs db pkg-id)
- (let ((res 0))
- (sqlite3:for-each-row
- (lambda (count)
- (set! res count))
- db
- "SELECT count(id) FROM refs WHERE pkg_id=?;"
- pkg-id)
- res))
-
-;; Create the sqlite db
-(define (datashare:open-db configdat)
- (let ((path (configf:lookup configdat "database" "location")))
- (if (and path
- (directory? path)
- (file-read-access? path))
- (let* ((dbpath (conc path "/datashare.db"))
- (writeable (file-write-access? dbpath))
- (dbexists (common:file-exists? dbpath))
- (handler (make-busy-timeout 136000)))
- (handle-exceptions
- exn
- (begin
- (debug:print 2 *default-log-port* "ERROR: problem accessing db " dbpath
- ((condition-property-accessor 'exn 'message) exn))
- (exit))
- (set! db (sqlite3:open-database dbpath)))
- (if *db-write-access* (sqlite3:set-busy-handler! db handler))
- (if (not dbexists)
- (begin
- (datashare:initialize-db db)))
- db)
- (print "ERROR: invalid path for storing database: " path))))
-
-(define (open-run-close-exception-handling proc idb . params)
- (handle-exceptions
- exn
- (let ((sleep-time (random 30))
- (err-status ((condition-property-accessor 'sqlite3 'status #f) exn)))
- (case err-status
- ((busy)
- (thread-sleep! sleep-time))
- (else
- (print "EXCEPTION: database overloaded or unreadable.")
- (print " message: " ((condition-property-accessor 'exn 'message) exn))
- (print "exn=" (condition->list exn))
- (print " status: " ((condition-property-accessor 'sqlite3 'status) exn))
- (print-call-chain (current-error-port))
- (thread-sleep! sleep-time)
- (print "trying db call one more time....this may never recover, if necessary kill process " (current-process-id) " on host " (get-host-name) " to clean up")))
- (apply open-run-close-exception-handling proc idb params))
- (apply open-run-close-no-exception-handling proc idb params)))
-
-(define (open-run-close-no-exception-handling proc idb . params)
- ;; (print "open-run-close-no-exception-handling START given a db=" (if idb "yes " "no ") ", params=" params)
- (let* ((db (cond
- ((sqlite3:database? idb) idb)
- ((not idb) (print "ERROR: cannot open-run-close with #f anymore"))
- ((procedure? idb) (idb))
- (else (print "ERROR: cannot open-run-close with #f anymore"))))
- (res #f))
- (set! res (apply proc db params))
- (if (not idb)(sqlite3:finalize! dbstruct))
- ;; (print "open-run-close-no-exception-handling END" )
- res))
-
-(define open-run-close open-run-close-no-exception-handling)
-
-(define (datashare:get-pkgs db area-filter version-filter iter-filter)
- (let ((res '()))
- (sqlite3:for-each-row ;; replace with fold ...
- (lambda (a . b)
- (set! res (cons (list->vector (cons a b)) res)))
- db
- (conc "SELECT id,area,version_name,store_type,copied,source_path,iteration,submitter,datetime,storegrp,datavol,quality,disk_id,comment,stored_path "
- " FROM pkgs WHERE area like ? AND version_name LIKE ? AND iteration " iter-filter ";")
- area-filter version-filter)
- (reverse res)))
-
-(define (datashare:get-pkg db area-name version-name #!key (iteration #f))
- (let ((dat '())
- (res #f))
- (sqlite3:for-each-row ;; replace with fold ...
- (lambda (a . b)
- (set! dat (cons (list->vector (cons a b)) dat)))
- db
- (conc "SELECT id,area,version_name,store_type,copied,source_path,iteration,submitter,datetime,storegrp,datavol,quality,disk_id,comment,stored_path "
- " FROM pkgs WHERE area=? AND version_name=? ORDER BY iteration ASC;")
- area-name version-name)
- ;; now filter for iteration, either max if #f or specific one
- (if (null? dat)
- #f
- (let loop ((hed (car dat))
- (tal (cdr dat))
- (cur 0))
- (let ((itr (datashare:pkg-get-iteration hed)))
- (if (equal? itr iteration) ;; this is the one if iteration is specified
- hed
- (if (null? tal)
- hed
- (loop (car tal)(cdr tal)))))))))
-
-(define (datashare:get-versions-for-area db area-name #!key (version-patt #f))
- (let ((res '())
- (data (make-hash-table)))
- (sqlite3:for-each-row
- (lambda (version-name submitter iteration submitted-time comment)
- ;; 0 1 2 3 4
- (hash-table-set! data version-name (vector version-name submitter iteration submitted-time comment)))
- db
- "SELECT version_name,submitter,iteration,datetime,comment FROM pkgs WHERE area='megatest' AND version_name != 'latest' AND version_name LIKE ? ORDER BY datetime asc;"
- (or version-patt "%"))
- (map (lambda (x)(hash-table-ref data x))(sort (hash-table-keys data) string-ci>=))))
-
-;;======================================================================
-;; DATA IMPORT/EXPORT
-;;======================================================================
-
-(define (datashare:import-data configdat source-path dest-path area version iteration)
- (let* ((space-avail (car dest-path))
- (disk-path (cdr dest-path))
- (targ-path (conc disk-path "/" area "/" version "/" iteration))
- (id (datashare:get-id db area version iteration))
- (db (datashare:open-db configdat)))
- (if (> space-avail 10000) ;; dumb heuristic
- (begin
- (create-directory targ-path #t)
- (datashare:set-stored-path db id targ-path)
- (print "Running command: rsync -av " source-path "/ " targ-path "/")
- (let ((th1 (make-thread (lambda ()
- (let ((pid (process-run "rsync" (list "-av" (conc source-path "/") (conc targ-path "/")))))
- (process-wait pid)
- (datashare:set-copied db id "yes")
- (sqlite3:finalize! db)))
- "Data copy")))
- (thread-start! th1))
- #t)
- (begin
- (print "ERROR: Not enough space in storage area " dest-path)
- (datashare:set-copied db id "no")
- (sqlite3:finalize! db)
- #f))))
-
-(define (datashare:get-areas configdat)
- (let* ((areadat (configf:get-section configdat "areas"))
- (areas (if areadat (map car areadat) '())))
- areas))
-
-(define (datashare:publish configdat publish-type area-name version comment spath submitter quality)
- ;; input checks
- (cond
- ((not (member area-name (datashare:get-areas configdat)))
- (cons #f (conc "Illegal area name \"" area-name "\"")))
- (else
- (let ((db (datashare:open-db configdat))
- (iteration (datashare:register-data db area-name version publish-type submitter quality spath comment))
- (dest-store (datashare:get-best-storage configdat)))
- (if iteration
- (if (eq? 'copy publish-type)
- (begin
- (datashare:import-data configdat spath dest-store area-name version iteration)
- (let ((id (datashare:get-id db area-name version iteration)))
- (datashare:set-latest db id area-name version iteration)))
- (let ((id (datashare:get-id db area-name version iteration)))
- (datashare:set-stored-path db id spath)
- (datashare:set-copied db id "yes")
- (datashare:set-copied db id "n/a")
- (datashare:set-latest db id area-name version iteration)))
- (print "ERROR: Failed to get an iteration number"))
- (sqlite3:finalize! db)
- (cons #t "Successfully saved data")))))
-
-(define (datashare:get-best-storage configdat)
- (let* ((storage (configf:lookup configdat "settings" "storage"))
- (store-areas (if storage (string-split storage) '())))
- (print "Looking for available space in " store-areas)
- (datashare:find-most-space store-areas)))
-
-;; (string->number (list-ref (with-input-from-pipe "df -B1000000 /tmp" (lambda ()(read-line)(string-split (read-line)))) 3))
-
-(define (datashare:find-most-space paths)
- (fold (lambda (area res)
- ;; (print "area=" area " res=" res)
- (let ((maxspace (car res))
- (currpath (cdr res)))
- ;; (print currpath " " maxspace)
- (if (file-write-access? area)
- (let ((currspace (string->number
- (list-ref
- (with-input-from-pipe
- ;; (conc "df --output=avail " area)
- (conc "df -B1000000 " area)
- ;; (lambda ()(read)(read))
- (lambda ()(read-line)(string-split (read-line))))
- 3))))
- (if (> currspace maxspace)
- (cons currspace area)
- res))
- res)))
- (cons 0 #f)
- paths))
-
-;; remove existing link and if possible ...
-;; create path to next of tip of target, create link back to source
-(define (datashare:build-dir-make-link source target)
- (if (common:file-exists? target)(datashare:backup-move target))
- (create-directory (pathname-directory target) #t)
- (create-symbolic-link source target))
-
-(define (datashare:backup-move path)
- (let* ((trashdir (conc (pathname-directory path) "/.trash"))
- (trashfile (conc trashdir "/" (current-seconds) "-" (pathname-file path))))
- (create-directory trashdir #t)
- (if (directory? path)
- (system (conc "mv " path " " trashfile))
- (file-move path trash-file))))
-
-;;======================================================================
-;; GUI
-;;======================================================================
-
-;; The main menu
-(define (datashare:main-menu)
- (iup:menu ;; a menu is a special attribute to a dialog (think Gnome putting the menu at screen top)
- (iup:menu-item "Files" (iup:menu ;; Note that you can use either #:action or action: for options
- (iup:menu-item "Open" action: (lambda (obj)
- (iup:show (iup:file-dialog))
- (print "File->open " obj)))
- (iup:menu-item "Save" #:action (lambda (obj)(print "File->save " obj)))
- (iup:menu-item "Exit" #:action (lambda (obj)(exit)))))
- (iup:menu-item "Tools" (iup:menu
- (iup:menu-item "Create new blah" #:action (lambda (obj)(print "Tools->new blah")))
- ;; (iup:menu-item "Show dialog" #:action (lambda (obj)
- ;; (show message-window
- ;; #:modal? #t
- ;; ;; set positon using coordinates or center, start, top, left, end, bottom, right, parent-center, current
- ;; ;; #:x 'mouse
- ;; ;; #:y 'mouse
- ;; )
- ))))
-
-(define (datashare:publish-view configdat)
- ;; (pp (hash-table->alist configdat))
- (let* ((areas (configf:get-section configdat "areas"))
- (label-size "70x")
- (areas-sel (iup:listbox #:expand "HORIZONTAL" #:dropdown "YES"))
- (version-tb (iup:textbox #:expand "HORIZONTAL")) ;; #:size "50x"))
- (areas-sel (iup:listbox #:expand "HORIZONTAL" #:dropdown "YES"))
- (component (iup:listbox #:expand "HORIZONTAL" #:dropdown "YES" ))
- (version-val (iup:textbox #:expand "HORIZONTAL" #:size "50x"))
- ;; (copy-link (iup:toggle #:expand "HORIZONTAL"))
- ;; (iteration (iup:textbox #:expand "YES" #:size "20x"))
- ;; (iteration (iup:textbox #:expand "HORIZONTAL" #:size "20x"))
- (area-filter (iup:textbox #:expand "HORIZONTAL" #:value "%"))
- (comment-tb (iup:textbox #:expand "YES" #:multiline "YES"))
- (source-tb (iup:textbox #:expand "HORIZONTAL"
- #:value (or (configf:lookup configdat "settings" "basepath")
- "")))
- (publish (lambda (publish-type)
- (let* ((area-num (or (string->number (iup:attribute areas-sel "VALUE")) 0))
- (area-dat (if (> area-num 0)(list-ref areas (- area-num 1))'("NOT SELECTED" "NOT SELECTED")))
- (area-path (cadr area-dat))
- (area-name (car area-dat))
- (version (iup:attribute version-tb "VALUE"))
- (comment (iup:attribute comment-tb "VALUE"))
- (spath (iup:attribute source-tb "VALUE"))
- (submitter (current-user-name))
- (quality 2))
- (datashare:publish configdat publish-type area-name version comment spath submitter quality))))
- (copy (iup:button "Copy and Publish"
- #:expand "HORIZONTAL"
- #:action (lambda (obj)
- (publish 'copy))))
- (link (iup:button "Link and Publish"
- #:expand "HORIZONTAL"
- #:action (lambda (obj)
- (publish 'link))))
- (browse-btn (iup:button "Browse"
- #:size "40x"
- #:action (lambda (obj)
- (let* ((fd (iup:file-dialog #:dialogtype "DIR"))
- (top (iup:show fd #:modal? "YES")))
- (iup:attribute-set! source-tb "VALUE"
- (iup:attribute fd "VALUE"))
- (iup:destroy! fd))))))
- (print "areas")
- ;; (pp areas)
- (fold (lambda (areadat num)
- ;; (print "Adding num=" num ", areadat=" areadat)
- (iup:attribute-set! areas-sel (conc num) (car areadat))
- (+ 1 num))
- 1 areas)
- (iup:vbox
- (iup:hbox (iup:label "Area:" #:size label-size) ;; area-filter
- areas-sel)
- (iup:hbox (iup:label "Version:" #:size label-size) version-tb)
- ;; (iup:hbox (iup:label "Link only" #:size label-size) copy-link)
- ;; (iup:label "Iteration:") iteration)
- (iup:hbox (iup:label "Comment:" #:size label-size) comment-tb)
- (iup:hbox (iup:label "Source base path:" #:size label-size) source-tb browse-btn)
- (iup:hbox copy link))))
-
-(define (datashare:lst->path pathlst)
- (conc "/" (string-intersperse (map conc pathlst) "/")))
-
-(define (datashare:path->lst path)
- (string-split path "/"))
-
-(define (datashare:pathdat-apply-heuristics configdat path)
- (cond
- ((common:file-exists? path) "found")
- (else (conc path " not installed"))))
-
-(define (datashare:get-view configdat)
- (iup:vbox
- (iup:hbox
- (let* ((label-size "60x")
- ;; filter elements
- (area-filter "%")
- (version-filter "%")
- (iter-filter ">= 0")
- ;; reverse lookup from path to data for src and installed
- (srcdat (make-hash-table)) ;; reverse lookup
- (installed-dat (make-hash-table))
- ;; config values
- (basepath (configf:lookup configdat "settings" "basepath"))
- ;; gui elements
- (submitter (iup:label "" #:expand "HORIZONTAL"))
- (date-submitted (iup:label "" #:expand "HORIZONTAL"))
- (comment (iup:label "" #:expand "HORIZONTAL"))
- (copy-link (iup:label "" #:expand "HORIZONTAL"))
- (quality (iup:label "" #:expand "HORIZONTAL"))
- (installed-status (iup:label "" #:expand "HORIZONTAL"))
- ;; misc
- (curr-record #f)
- ;; (source-data (iup:label "" #:expand "HORIZONTAL"))
- (tb (iup:treebox
- #:value 0
- #:name "Packages"
- #:expand "YES"
- #:addexpanded "NO"
- #:selection-cb
- (lambda (obj id state)
- ;; (print "obj: " obj ", id: " id ", state: " state)
- (let* ((path (datashare:lst->path (cdr (tree:node->path obj id))))
- (record (hash-table-ref/default srcdat path #f)))
- (if record
- (begin
- (set! curr-record record)
- (iup:attribute-set! submitter "TITLE" (datashare:pkg-get-submitter record))
- (iup:attribute-set! date-submitted "TITLE" (time->string (seconds->local-time (datashare:pkg-get-datetime record))))
- (iup:attribute-set! comment "TITLE" (datashare:pkg-get-comment record))
- (iup:attribute-set! quality "TITLE" (datashare:pkg-get-quality record))
- (iup:attribute-set! copy-link "TITLE" (datashare:pkg-get-store_type record))
- ))
- ;; (print "id=" id " path=" path " record=" record);; (tree:node->path obj id) " run-id: " run-id)
- ))))
- (tb2 (iup:treebox
- #:value 0
- #:name "Installed"
- #:expand "YES"
- #:addexpanded "NO"
- #:selection-cb
- (lambda (obj id state)
- ;; (print "obj: " obj ", id: " id ", state: " state)
- (let* ((path (datashare:lst->path (cdr (tree:node->path obj id))))
- (status (hash-table-ref/default installed-dat path #f)))
- (iup:attribute-set! installed-status "TITLE" (if status status ""))
- ))))
- (refresh (lambda (obj)
- (let* ((db (datashare:open-db configdat))
- (areas (or (configf:get-section configdat "areas") '())))
- ;;
- ;; first update the Sources
- ;;
- (for-each
- (lambda (pkgitem)
- (let* ((pkg-path (list (datashare:pkg-get-area pkgitem)
- (datashare:pkg-get-version_name pkgitem)
- (datashare:pkg-get-iteration pkgitem)))
- (pkg-id (datashare:pkg-get-id pkgitem))
- (path (datashare:lst->path pkg-path)))
- ;; (print "tree:add-node tb=" tb ", pkg-path=" pkg-path ", pkg-id=" pkg-id)
- (if (not (hash-table-ref/default srcdat path #f))
- (tree:add-node tb "Packages" pkg-path userdata: (conc "pkg-id: " pkg-id)))
- ;; (print "path=" path " pkgitem=" pkgitem)
- (hash-table-set! srcdat path pkgitem)))
- (datashare:get-pkgs db area-filter version-filter iter-filter))
- ;;
- ;; then update the installed
- ;;
- (for-each
- (lambda (area)
- (let* ((path (conc "/" (cadr area)))
- (fullpath (conc basepath path)))
- (if (not (hash-table-ref/default installed-dat path #f))
- (tree:add-node tb2 "Installed" (datashare:path->lst path)))
- (hash-table-set! installed-dat path (datashare:pathdat-apply-heuristics configdat fullpath))))
- areas)
- (sqlite3:finalize! db))))
- (apply (iup:button "Apply"
- #:action
- (lambda (obj)
- (if curr-record
- (let* ((area (datashare:pkg-get-area curr-record))
- (stored-path (datashare:pkg-get-stored_path curr-record))
- (source-type (datashare:pkg-get-store_type curr-record))
- (source-path (case source-type ;; (equal? source-type "link"))
- ((link)(datashare:pkg-get-source-path curr-record))
- ((copy)stored-path)
- (else #f)))
- (dest-stub (configf:lookup configdat "areas" area))
- (target-path (conc basepath "/" dest-stub)))
- (datashare:build-dir-make-link stored-path target-path)
- (print "Creating link from " stored-path " to " target-path)))))))
- (iup:vbox
- (iup:hbox tb tb2)
- (iup:frame
- #:title "Source Info"
- (iup:vbox
- (iup:hbox (iup:button "Refresh" #:action refresh) apply)
- (iup:hbox (iup:label "Submitter: ") ;; #:size label-size)
- submitter
- (iup:label "Submitted on: ") ;; #:size label-size)
- date-submitted)
- (iup:hbox (iup:label "Data stored: ")
- copy-link
- (iup:label "Quality: ")
- quality)
- (iup:hbox (iup:label "Comment: ")
- comment)))
- (iup:frame
- #:title "Installed Info"
- (iup:vbox
- (iup:hbox (iup:label "Installed status/path: ") installed-status)))
- )))))
-
-(define (datashare:manage-view configdat)
- (iup:vbox
- (iup:hbox
- (iup:button "Pushme"
- #:expand "YES"
- ))))
-
-(define (datashare:gui configdat)
- (iup:show
- (iup:dialog
- #:title (conc "DataShare dashboard " (current-user-name) ":" (current-directory))
- #:menu (datashare:main-menu)
- (let* ((tabs (iup:tabs
- #:tabchangepos-cb (lambda (obj curr prev)
- (set! *datashare:current-tab-number* curr))
- (datashare:publish-view configdat)
- (datashare:get-view configdat)
- (datashare:manage-view configdat)
- )))
- ;; (set! (iup:callback tabs tabchange-cb:) (lambda (a b c)(print "SWITCHED TO TAB: " a " " b " " c)))
- (iup:attribute-set! tabs "TABTITLE0" "Publish")
- (iup:attribute-set! tabs "TABTITLE1" "Get")
- (iup:attribute-set! tabs "TABTITLE2" "Manage")
- ;; (iup:attribute-set! tabs "BGCOLOR" "190 190 190")
- tabs)))
- (iup:main-loop))
-
-;;======================================================================
-;; MISC
-;;======================================================================
-
-
-(define (datashare:do-as-calling-user proc)
- (let ((eid (current-effective-user-id))
- (cid (current-user-id)))
- (if (not (eq? eid cid)) ;; running suid
- (set! (current-effective-user-id) cid))
- ;; (print "running as " (current-effective-user-id))
- (proc)
- (if (not (eq? eid cid))
- (set! (current-effective-user-id) eid))))
-
-(define (datashare:find name paths)
- (if (null? paths)
- #f
- (let loop ((hed (car paths))
- (tal (cdr paths)))
- (if (common:file-exists? (conc hed "/" name))
- hed
- (if (null? tal)
- #f
- (loop (car tal)(cdr tal)))))))
-
-;;======================================================================
-;; MAIN
-;;======================================================================
-
-(define (datashare:load-config exe-dir exe-name)
- (let* ((fname (conc exe-dir "/." exe-name ".config")))
- (ini:property-separator-patt " * *")
- (ini:property-separator #\space)
- (if (common:file-exists? fname)
- ;; (ini:read-ini fname)
- (read-config fname #f #t)
- (make-hash-table))))
-
-(define (datashare:process-action configdat action . args)
- (case (string->symbol action)
- ((get)
- (if (< (length args) 2)
- (begin
- (print "ERROR: Missing arguments; " (string-intersperse args ", "))
- (exit 1))
- (let* ((basepath (configf:lookup configdat "settings" "basepath"))
- (db (datashare:open-db configdat))
- (area (car args))
- (version (cadr args)) ;; iteration
- (remargs (args:get-args args '("-i") '() args:arg-hash 0))
- (iteration (if (args:get-arg "-i")(string->number (args:get-arg "-i")) #f))
- (curr-record (datashare:get-pkg db area version iteration: iteration)))
- (if (not curr-record)
- (begin
- (print "ERROR: No matching record found; area=" area ", version=" version ", iteration=" (if iteration iteration "(max)"))
- (exit 1))
- (let* ((stored-path (datashare:pkg-get-stored_path curr-record))
- (source-type (datashare:pkg-get-store_type curr-record))
- (source-path (case source-type ;; (equal? source-type "link"))
- ((link) (datashare:pkg-get-source-path curr-record))
- ((copy) stored-path)
- (else #f)))
- (dest-stub (configf:lookup configdat "areas" area))
- (target-path (conc basepath "/" dest-stub)))
- (datashare:build-dir-make-link stored-path target-path)
- (datashare:record-pkg-ref db (datashare:pkg-get-id curr-record) target-path)
- (sqlite3:finalize! db)
- (print "Creating link from " stored-path " to " target-path))))))
- ((publish)
- (if (< (length args) 3)
- (begin
- (print "ERROR: Missing arguments; " (string-intersperse args ", "))
- (exit 1))
- (let* ((srcpath (list-ref args 0))
- (areaname (list-ref args 1))
- (version (list-ref args 2))
- (remargs (args:get-args (drop args 2)
- '("-type" ;; link or copy (default is copy)
- "-m")
- '()
- args:arg-hash
- 0))
- (publish-type (if (equal? (args:get-arg "-type") "link") 'link 'copy))
- (comment (or (args:get-arg "-m") ""))
- (submitter (current-user-name))
- (quality (args:get-arg "-quality"))
- (publish-res (datashare:publish configdat publish-type areaname version comment srcpath submitter quality)))
- (if (not (car publish-res))
- (begin
- (print "ERROR: " (cdr publish-res))
- (exit 1))))))
- ((list-versions)
- (let ((area-name (car args)) ;; version patt full print
- (remargs (args:get-args args '("-vpatt") '("-full") args:arg-hash 0))
- (db (datashare:open-db configdat))
- (versions (datashare:get-versions-for-area db (car args) version-patt: (args:get-arg "-vpatt"))))
- ;; (print "area-name=" area-name " args=" args " *args-hash*=" (hash-table->alist *args-hash*))
- (map (lambda (x)
- (if (args:get-arg "-full")
- (format #t
- "~10a~10a~4a~27a~30a\n"
- (vector-ref x 0)
- (vector-ref x 1)
- (vector-ref x 2)
- (conc "\"" (time->string (seconds->local-time (vector-ref x 3))) "\"")
- (conc "\"" (vector-ref x 4) "\""))
- (print (vector-ref x 0))))
- versions)
- (sqlite3:finalize! db)))))
-
-;; ease debugging by loading ~/.dashboardrc - REMOVE FROM PRODUCTION!
-(let ((debugcontrolf (conc (get-environment-variable "HOME") "/.datasharerc")))
- (if (common:file-exists? debugcontrolf)
- (load debugcontrolf)))
-
-(define (main)
- (let* ((args (argv))
- (prog (car args))
- (rema (cdr args))
- (exe-name (pathname-file (car (argv))))
- (exe-dir (or (pathname-directory prog)
- (datashare:find exe-name (string-split (get-environment-variable "PATH") ":"))))
- (configdat (datashare:load-config exe-dir exe-name)))
- (cond
- ;; one-word commands
- ((eq? (length rema) 1)
- (case (string->symbol (car rema))
- ((help -h -help --h --help)
- (print datashare:help))
- ((list-areas)
- (map print (datashare:get-areas configdat)))
- (else
- (print "ERROR: Unrecognised command. Try \"datashare help\""))))
- ;; multi-word commands
- ((null? rema)(datashare:gui configdat))
- ((>= (length rema) 2)
- (apply datashare:process-action configdat (car rema)(cdr rema)))
- (else (print "ERROR: Unrecognised command. Try \"datashare help\"")))))
-
-(main)
+;; ==> (module datashare
+;; ==> (use ssax)
+;; ==> (use sxml-serializer)
+;; ==> (use sxml-modifications)
+;; ==> (use regex)
+;; ==> (use srfi-69)
+;; ==> (use regex-case)
+;; ==> (use posix)
+;; ==> (use json)
+;; ==> (use csv)
+;; ==> (use srfi-18)
+;; ==> (use format)
+;; ==>
+;; ==> (use (prefix iup iup:))
+;; ==> (import (prefix ini-file ini:))
+;; ==>
+;; ==> (use canvas-draw)
+;; ==> (import canvas-draw-iup)
+;; ==>
+;; ==> (use sqlite3 srfi-1 posix regex regex-case srfi-69)
+;; ==> (import (prefix sqlite3 sqlite3:))
+;; ==>
+;; ==> (declare (uses configf))
+;; ==> (declare (uses tree))
+;; ==> (declare (uses margs))
+;; ==> ;; (declare (uses dcommon))
+;; ==> ;; (declare (uses launch))
+;; ==> ;; (declare (uses gutils))
+;; ==> ;; (declare (uses db))
+;; ==> ;; (declare (uses synchash))
+;; ==> ;; (declare (uses server))
+;; ==> ;; (declare (uses megatest-version))
+;; ==> ;; (declare (uses tbd))
+;; ==>
+;; ==> (include "megatest-fossil-hash.scm")
+;; ==>
+;; ==> ;;
+;; ==> ;; GLOBALS
+;; ==> ;;
+;; ==> (define *datashare:current-tab-number* 0)
+;; ==> (define *args-hash* (make-hash-table))
+;; ==> (define datashare:help (conc "Usage: datashare [action [params ...]]
+;; ==>
+;; ==> Note: run datashare without parameters to start the gui.
+;; ==>
+;; ==> list-areas : List the allowed areas
+;; ==>
+;; ==> list-versions : List versions available in
+;; ==> options : -full, -vpatt patt
+;; ==>
+;; ==> publish : Publish data for area and with version
+;; ==>
+;; ==> get : Get a link to data, put the link in destpath
+;; ==> options : -i iteration
+;; ==>
+;; ==> update : Update the link to data to the latest iteration.
+;; ==>
+;; ==> Part of the Megatest tool suite.
+;; ==> Learn more at http://www.kiatoa.com/fossils/megatest
+;; ==>
+;; ==> Version: " megatest-fossil-hash)) ;; "
+;; ==>
+;; ==> ;;======================================================================
+;; ==> ;; RECORDS
+;; ==> ;;======================================================================
+;; ==>
+;; ==> ;; make-vector-record "testing" datastore pkg id area version_name store_type copied source_path iteration submitter datetime storegrp datavol quality disk_id comment
+;; ==> ;; testing
+;; ==> (define (make-datashare:pkg)(make-vector 15))
+;; ==> (define-inline (datashare:pkg-get-id vec) (vector-ref vec 0))
+;; ==> (define-inline (datashare:pkg-get-area vec) (vector-ref vec 1))
+;; ==> (define-inline (datashare:pkg-get-version_name vec) (vector-ref vec 2))
+;; ==> (define-inline (datashare:pkg-get-store_type vec) (vector-ref vec 3))
+;; ==> (define-inline (datashare:pkg-get-copied vec) (vector-ref vec 4))
+;; ==> (define-inline (datashare:pkg-get-source_path vec) (vector-ref vec 5))
+;; ==> (define-inline (datashare:pkg-get-iteration vec) (vector-ref vec 6))
+;; ==> (define-inline (datashare:pkg-get-submitter vec) (vector-ref vec 7))
+;; ==> (define-inline (datashare:pkg-get-datetime vec) (vector-ref vec 8))
+;; ==> (define-inline (datashare:pkg-get-storegrp vec) (vector-ref vec 9))
+;; ==> (define-inline (datashare:pkg-get-datavol vec) (vector-ref vec 10))
+;; ==> (define-inline (datashare:pkg-get-quality vec) (vector-ref vec 11))
+;; ==> (define-inline (datashare:pkg-get-disk_id vec) (vector-ref vec 12))
+;; ==> (define-inline (datashare:pkg-get-comment vec) (vector-ref vec 13))
+;; ==> (define-inline (datashare:pkg-get-stored_path vec) (vector-ref vec 14))
+;; ==> (define-inline (datashare:pkg-set-id! vec val)(vector-set! vec 0 val))
+;; ==> (define-inline (datashare:pkg-set-area! vec val)(vector-set! vec 1 val))
+;; ==> (define-inline (datashare:pkg-set-version_name! vec val)(vector-set! vec 2 val))
+;; ==> (define-inline (datashare:pkg-set-store_type! vec val)(vector-set! vec 3 val))
+;; ==> (define-inline (datashare:pkg-set-copied! vec val)(vector-set! vec 4 val))
+;; ==> (define-inline (datashare:pkg-set-source_path! vec val)(vector-set! vec 5 val))
+;; ==> (define-inline (datashare:pkg-set-iteration! vec val)(vector-set! vec 6 val))
+;; ==> (define-inline (datashare:pkg-set-submitter! vec val)(vector-set! vec 7 val))
+;; ==> (define-inline (datashare:pkg-set-datetime! vec val)(vector-set! vec 8 val))
+;; ==> (define-inline (datashare:pkg-set-storegrp! vec val)(vector-set! vec 9 val))
+;; ==> (define-inline (datashare:pkg-set-datavol! vec val)(vector-set! vec 10 val))
+;; ==> (define-inline (datashare:pkg-set-quality! vec val)(vector-set! vec 11 val))
+;; ==> (define-inline (datashare:pkg-set-disk_id! vec val)(vector-set! vec 12 val))
+;; ==> (define-inline (datashare:pkg-set-comment! vec val)(vector-set! vec 13 val))
+;; ==> (define-inline (datashare:pkg-set-stored_path! vec val)(vector-set! vec 14 val))
+;; ==>
+;; ==> ;;======================================================================
+;; ==> ;; DB
+;; ==> ;;======================================================================
+;; ==>
+;; ==> (define (datashare:initialize-db db)
+;; ==> (for-each
+;; ==> (lambda (qry)
+;; ==> (sqlite3:execute db qry))
+;; ==> (list
+;; ==> "CREATE TABLE pkgs
+;; ==> (id INTEGER PRIMARY KEY,
+;; ==> area TEXT,
+;; ==> version_name TEXT,
+;; ==> store_type TEXT DEFAULT 'copy',
+;; ==> copied INTEGER DEFAULT 0,
+;; ==> source_path TEXT,
+;; ==> stored_path TEXT,
+;; ==> iteration INTEGER DEFAULT 0,
+;; ==> submitter TEXT,
+;; ==> datetime TIMESTAMP DEFAULT (strftime('%s','now')),
+;; ==> storegrp TEXT,
+;; ==> datavol INTEGER,
+;; ==> quality TEXT,
+;; ==> disk_id INTEGER,
+;; ==> comment TEXT);"
+;; ==> "CREATE TABLE refs
+;; ==> (id INTEGER PRIMARY KEY,
+;; ==> pkg_id INTEGER,
+;; ==> destlink TEXT);"
+;; ==> "CREATE TABLE disks
+;; ==> (id INTEGER PRIMARY KEY,
+;; ==> storegrp TEXT,
+;; ==> path TEXT);")))
+;; ==>
+;; ==> (define (datashare:register-data db area version-name store-type submitter quality source-path comment)
+;; ==> (let ((iter-qry (sqlite3:prepare db "SELECT max(iteration) FROM pkgs WHERE area=? AND version_name=?;"))
+;; ==> (next-iteration 0))
+;; ==> (sqlite3:with-transaction
+;; ==> db
+;; ==> (lambda ()
+;; ==> (sqlite3:for-each-row
+;; ==> (lambda (iteration)
+;; ==> (if (and (number? iteration)
+;; ==> (>= iteration next-iteration))
+;; ==> (set! next-iteration (+ iteration 1))))
+;; ==> iter-qry area version-name)
+;; ==> ;; now store the data
+;; ==> (sqlite3:execute db "INSERT INTO pkgs (area,version_name,iteration,store_type,submitter,source_path,quality,comment)
+;; ==> VALUES (?,?,?,?,?,?,?,?);"
+;; ==> area version-name next-iteration (conc store-type) submitter source-path quality comment)))
+;; ==> (sqlite3:finalize! iter-qry)
+;; ==> next-iteration))
+;; ==>
+;; ==> (define (datashare:get-id db area version-name iteration)
+;; ==> (let ((res #f))
+;; ==> (sqlite3:for-each-row
+;; ==> (lambda (id)
+;; ==> (set! res id))
+;; ==> db
+;; ==> "SELECT id FROM pkgs WHERE area=? AND version_name=? AND iteration=?;"
+;; ==> area version-name iteration)
+;; ==> res))
+;; ==>
+;; ==> (define (datashare:set-stored-path db id path)
+;; ==> (sqlite3:execute db "UPDATE pkgs SET stored_path=? WHERE id=?;" path id))
+;; ==>
+;; ==> (define (datashare:set-copied db id value)
+;; ==> (sqlite3:execute db "UPDATE pkgs SET copied=? WHERE id=?;" value id))
+;; ==>
+;; ==> (define (datashare:get-pkg-record db area version-name iteration)
+;; ==> (let ((res #f))
+;; ==> (sqlite3:for-each-row
+;; ==> (lambda (a . b)
+;; ==> (set! res (apply vector a b)))
+;; ==> db
+;; ==> "SELECT * FROM pkgs WHERE area=? AND version_name=? AND iteration=?;"
+;; ==> area
+;; ==> version-name
+;; ==> iteration)
+;; ==> res))
+;; ==>
+;; ==> ;; take version-name iteration and register or update "lastest/0"
+;; ==> ;;
+;; ==> (define (datashare:set-latest db id area version-name iteration)
+;; ==> (let* ((rec (datashare:get-pkg-record db area version-name iteration))
+;; ==> (latest-id (datashare:get-id db area "latest" 0))
+;; ==> (stored-path (datashare:pkg-get-stored_path rec)))
+;; ==> (if latest-id ;; have a record - bump the link pointer
+;; ==> (datashare:set-stored-path db latest-id stored-path)
+;; ==> (datashare:register-data db area "latest" 'link "auto" "na" stored-path "latest data"))))
+;; ==>
+;; ==> ;; set a package ref, this is the location where the link back to the stored data
+;; ==> ;; is put.
+;; ==> ;;
+;; ==> ;; if there is nothing at that location then the record can be removed
+;; ==> ;; if there are no refs for a particular pkg-id then that pkg-id is a
+;; ==> ;; candidate for removal
+;; ==> ;;
+;; ==> (define (datashare:record-pkg-ref db pkg-id dest-link)
+;; ==> (sqlite3:execute db "INSERT INTO refs (pkg_id,destlink) VALUES (?,?);" pkg-id dest-link))
+;; ==>
+;; ==> (define (datashare:count-refs db pkg-id)
+;; ==> (let ((res 0))
+;; ==> (sqlite3:for-each-row
+;; ==> (lambda (count)
+;; ==> (set! res count))
+;; ==> db
+;; ==> "SELECT count(id) FROM refs WHERE pkg_id=?;"
+;; ==> pkg-id)
+;; ==> res))
+;; ==>
+;; ==> ;; Create the sqlite db
+;; ==> (define (datashare:open-db configdat)
+;; ==> (let ((path (configf:lookup configdat "database" "location")))
+;; ==> (if (and path
+;; ==> (directory? path)
+;; ==> (file-read-access? path))
+;; ==> (let* ((dbpath (conc path "/datashare.db"))
+;; ==> (writeable (file-write-access? dbpath))
+;; ==> (dbexists (common:file-exists? dbpath))
+;; ==> (handler (make-busy-timeout 136000)))
+;; ==> (handle-exceptions
+;; ==> exn
+;; ==> (begin
+;; ==> (debug:print 2 *default-log-port* "ERROR: problem accessing db " dbpath
+;; ==> ((condition-property-accessor 'exn 'message) exn))
+;; ==> (exit))
+;; ==> (set! db (sqlite3:open-database dbpath)))
+;; ==> (if *db-write-access* (sqlite3:set-busy-handler! db handler))
+;; ==> (if (not dbexists)
+;; ==> (begin
+;; ==> (datashare:initialize-db db)))
+;; ==> db)
+;; ==> (print "ERROR: invalid path for storing database: " path))))
+;; ==>
+;; ==> (define (open-run-close-exception-handling proc idb . params)
+;; ==> (handle-exceptions
+;; ==> exn
+;; ==> (let ((sleep-time (random 30))
+;; ==> (err-status ((condition-property-accessor 'sqlite3 'status #f) exn)))
+;; ==> (case err-status
+;; ==> ((busy)
+;; ==> (thread-sleep! sleep-time))
+;; ==> (else
+;; ==> (print "EXCEPTION: database overloaded or unreadable.")
+;; ==> (print " message: " ((condition-property-accessor 'exn 'message) exn))
+;; ==> (print "exn=" (condition->list exn))
+;; ==> (print " status: " ((condition-property-accessor 'sqlite3 'status) exn))
+;; ==> (print-call-chain (current-error-port))
+;; ==> (thread-sleep! sleep-time)
+;; ==> (print "trying db call one more time....this may never recover, if necessary kill process " (current-process-id) " on host " (get-host-name) " to clean up")))
+;; ==> (apply open-run-close-exception-handling proc idb params))
+;; ==> (apply open-run-close-no-exception-handling proc idb params)))
+;; ==>
+;; ==> (define (open-run-close-no-exception-handling proc idb . params)
+;; ==> ;; (print "open-run-close-no-exception-handling START given a db=" (if idb "yes " "no ") ", params=" params)
+;; ==> (let* ((db (cond
+;; ==> ((sqlite3:database? idb) idb)
+;; ==> ((not idb) (print "ERROR: cannot open-run-close with #f anymore"))
+;; ==> ((procedure? idb) (idb))
+;; ==> (else (print "ERROR: cannot open-run-close with #f anymore"))))
+;; ==> (res #f))
+;; ==> (set! res (apply proc db params))
+;; ==> (if (not idb)(sqlite3:finalize! dbstruct))
+;; ==> ;; (print "open-run-close-no-exception-handling END" )
+;; ==> res))
+;; ==>
+;; ==> (define open-run-close open-run-close-no-exception-handling)
+;; ==>
+;; ==> (define (datashare:get-pkgs db area-filter version-filter iter-filter)
+;; ==> (let ((res '()))
+;; ==> (sqlite3:for-each-row ;; replace with fold ...
+;; ==> (lambda (a . b)
+;; ==> (set! res (cons (list->vector (cons a b)) res)))
+;; ==> db
+;; ==> (conc "SELECT id,area,version_name,store_type,copied,source_path,iteration,submitter,datetime,storegrp,datavol,quality,disk_id,comment,stored_path "
+;; ==> " FROM pkgs WHERE area like ? AND version_name LIKE ? AND iteration " iter-filter ";")
+;; ==> area-filter version-filter)
+;; ==> (reverse res)))
+;; ==>
+;; ==> (define (datashare:get-pkg db area-name version-name #!key (iteration #f))
+;; ==> (let ((dat '())
+;; ==> (res #f))
+;; ==> (sqlite3:for-each-row ;; replace with fold ...
+;; ==> (lambda (a . b)
+;; ==> (set! dat (cons (list->vector (cons a b)) dat)))
+;; ==> db
+;; ==> (conc "SELECT id,area,version_name,store_type,copied,source_path,iteration,submitter,datetime,storegrp,datavol,quality,disk_id,comment,stored_path "
+;; ==> " FROM pkgs WHERE area=? AND version_name=? ORDER BY iteration ASC;")
+;; ==> area-name version-name)
+;; ==> ;; now filter for iteration, either max if #f or specific one
+;; ==> (if (null? dat)
+;; ==> #f
+;; ==> (let loop ((hed (car dat))
+;; ==> (tal (cdr dat))
+;; ==> (cur 0))
+;; ==> (let ((itr (datashare:pkg-get-iteration hed)))
+;; ==> (if (equal? itr iteration) ;; this is the one if iteration is specified
+;; ==> hed
+;; ==> (if (null? tal)
+;; ==> hed
+;; ==> (loop (car tal)(cdr tal)))))))))
+;; ==>
+;; ==> (define (datashare:get-versions-for-area db area-name #!key (version-patt #f))
+;; ==> (let ((res '())
+;; ==> (data (make-hash-table)))
+;; ==> (sqlite3:for-each-row
+;; ==> (lambda (version-name submitter iteration submitted-time comment)
+;; ==> ;; 0 1 2 3 4
+;; ==> (hash-table-set! data version-name (vector version-name submitter iteration submitted-time comment)))
+;; ==> db
+;; ==> "SELECT version_name,submitter,iteration,datetime,comment FROM pkgs WHERE area='megatest' AND version_name != 'latest' AND version_name LIKE ? ORDER BY datetime asc;"
+;; ==> (or version-patt "%"))
+;; ==> (map (lambda (x)(hash-table-ref data x))(sort (hash-table-keys data) string-ci>=))))
+;; ==>
+;; ==> ;;======================================================================
+;; ==> ;; DATA IMPORT/EXPORT
+;; ==> ;;======================================================================
+;; ==>
+;; ==> (define (datashare:import-data configdat source-path dest-path area version iteration)
+;; ==> (let* ((space-avail (car dest-path))
+;; ==> (disk-path (cdr dest-path))
+;; ==> (targ-path (conc disk-path "/" area "/" version "/" iteration))
+;; ==> (id (datashare:get-id db area version iteration))
+;; ==> (db (datashare:open-db configdat)))
+;; ==> (if (> space-avail 10000) ;; dumb heuristic
+;; ==> (begin
+;; ==> (create-directory targ-path #t)
+;; ==> (datashare:set-stored-path db id targ-path)
+;; ==> (print "Running command: rsync -av " source-path "/ " targ-path "/")
+;; ==> (let ((th1 (make-thread (lambda ()
+;; ==> (let ((pid (process-run "rsync" (list "-av" (conc source-path "/") (conc targ-path "/")))))
+;; ==> (process-wait pid)
+;; ==> (datashare:set-copied db id "yes")
+;; ==> (sqlite3:finalize! db)))
+;; ==> "Data copy")))
+;; ==> (thread-start! th1))
+;; ==> #t)
+;; ==> (begin
+;; ==> (print "ERROR: Not enough space in storage area " dest-path)
+;; ==> (datashare:set-copied db id "no")
+;; ==> (sqlite3:finalize! db)
+;; ==> #f))))
+;; ==>
+;; ==> (define (datashare:get-areas configdat)
+;; ==> (let* ((areadat (configf:get-section configdat "areas"))
+;; ==> (areas (if areadat (map car areadat) '())))
+;; ==> areas))
+;; ==>
+;; ==> (define (datashare:publish configdat publish-type area-name version comment spath submitter quality)
+;; ==> ;; input checks
+;; ==> (cond
+;; ==> ((not (member area-name (datashare:get-areas configdat)))
+;; ==> (cons #f (conc "Illegal area name \"" area-name "\"")))
+;; ==> (else
+;; ==> (let ((db (datashare:open-db configdat))
+;; ==> (iteration (datashare:register-data db area-name version publish-type submitter quality spath comment))
+;; ==> (dest-store (datashare:get-best-storage configdat)))
+;; ==> (if iteration
+;; ==> (if (eq? 'copy publish-type)
+;; ==> (begin
+;; ==> (datashare:import-data configdat spath dest-store area-name version iteration)
+;; ==> (let ((id (datashare:get-id db area-name version iteration)))
+;; ==> (datashare:set-latest db id area-name version iteration)))
+;; ==> (let ((id (datashare:get-id db area-name version iteration)))
+;; ==> (datashare:set-stored-path db id spath)
+;; ==> (datashare:set-copied db id "yes")
+;; ==> (datashare:set-copied db id "n/a")
+;; ==> (datashare:set-latest db id area-name version iteration)))
+;; ==> (print "ERROR: Failed to get an iteration number"))
+;; ==> (sqlite3:finalize! db)
+;; ==> (cons #t "Successfully saved data")))))
+;; ==>
+;; ==> (define (datashare:get-best-storage configdat)
+;; ==> (let* ((storage (configf:lookup configdat "settings" "storage"))
+;; ==> (store-areas (if storage (string-split storage) '())))
+;; ==> (print "Looking for available space in " store-areas)
+;; ==> (datashare:find-most-space store-areas)))
+;; ==>
+;; ==> ;; (string->number (list-ref (with-input-from-pipe "df -B1000000 /tmp" (lambda ()(read-line)(string-split (read-line)))) 3))
+;; ==>
+;; ==> (define (datashare:find-most-space paths)
+;; ==> (fold (lambda (area res)
+;; ==> ;; (print "area=" area " res=" res)
+;; ==> (let ((maxspace (car res))
+;; ==> (currpath (cdr res)))
+;; ==> ;; (print currpath " " maxspace)
+;; ==> (if (file-write-access? area)
+;; ==> (let ((currspace (string->number
+;; ==> (list-ref
+;; ==> (with-input-from-pipe
+;; ==> ;; (conc "df --output=avail " area)
+;; ==> (conc "df -B1000000 " area)
+;; ==> ;; (lambda ()(read)(read))
+;; ==> (lambda ()(read-line)(string-split (read-line))))
+;; ==> 3))))
+;; ==> (if (> currspace maxspace)
+;; ==> (cons currspace area)
+;; ==> res))
+;; ==> res)))
+;; ==> (cons 0 #f)
+;; ==> paths))
+;; ==>
+;; ==> ;; remove existing link and if possible ...
+;; ==> ;; create path to next of tip of target, create link back to source
+;; ==> (define (datashare:build-dir-make-link source target)
+;; ==> (if (common:file-exists? target)(datashare:backup-move target))
+;; ==> (create-directory (pathname-directory target) #t)
+;; ==> (create-symbolic-link source target))
+;; ==>
+;; ==> (define (datashare:backup-move path)
+;; ==> (let* ((trashdir (conc (pathname-directory path) "/.trash"))
+;; ==> (trashfile (conc trashdir "/" (current-seconds) "-" (pathname-file path))))
+;; ==> (create-directory trashdir #t)
+;; ==> (if (directory? path)
+;; ==> (system (conc "mv " path " " trashfile))
+;; ==> (file-move path trash-file))))
+;; ==>
+;; ==> ;;======================================================================
+;; ==> ;; GUI
+;; ==> ;;======================================================================
+;; ==>
+;; ==> ;; The main menu
+;; ==> (define (datashare:main-menu)
+;; ==> (iup:menu ;; a menu is a special attribute to a dialog (think Gnome putting the menu at screen top)
+;; ==> (iup:menu-item "Files" (iup:menu ;; Note that you can use either #:action or action: for options
+;; ==> (iup:menu-item "Open" action: (lambda (obj)
+;; ==> (iup:show (iup:file-dialog))
+;; ==> (print "File->open " obj)))
+;; ==> (iup:menu-item "Save" #:action (lambda (obj)(print "File->save " obj)))
+;; ==> (iup:menu-item "Exit" #:action (lambda (obj)(exit)))))
+;; ==> (iup:menu-item "Tools" (iup:menu
+;; ==> (iup:menu-item "Create new blah" #:action (lambda (obj)(print "Tools->new blah")))
+;; ==> ;; (iup:menu-item "Show dialog" #:action (lambda (obj)
+;; ==> ;; (show message-window
+;; ==> ;; #:modal? #t
+;; ==> ;; ;; set positon using coordinates or center, start, top, left, end, bottom, right, parent-center, current
+;; ==> ;; ;; #:x 'mouse
+;; ==> ;; ;; #:y 'mouse
+;; ==> ;; )
+;; ==> ))))
+;; ==>
+;; ==> (define (datashare:publish-view configdat)
+;; ==> ;; (pp (hash-table->alist configdat))
+;; ==> (let* ((areas (configf:get-section configdat "areas"))
+;; ==> (label-size "70x")
+;; ==> (areas-sel (iup:listbox #:expand "HORIZONTAL" #:dropdown "YES"))
+;; ==> (version-tb (iup:textbox #:expand "HORIZONTAL")) ;; #:size "50x"))
+;; ==> (areas-sel (iup:listbox #:expand "HORIZONTAL" #:dropdown "YES"))
+;; ==> (component (iup:listbox #:expand "HORIZONTAL" #:dropdown "YES" ))
+;; ==> (version-val (iup:textbox #:expand "HORIZONTAL" #:size "50x"))
+;; ==> ;; (copy-link (iup:toggle #:expand "HORIZONTAL"))
+;; ==> ;; (iteration (iup:textbox #:expand "YES" #:size "20x"))
+;; ==> ;; (iteration (iup:textbox #:expand "HORIZONTAL" #:size "20x"))
+;; ==> (area-filter (iup:textbox #:expand "HORIZONTAL" #:value "%"))
+;; ==> (comment-tb (iup:textbox #:expand "YES" #:multiline "YES"))
+;; ==> (source-tb (iup:textbox #:expand "HORIZONTAL"
+;; ==> #:value (or (configf:lookup configdat "settings" "basepath")
+;; ==> "")))
+;; ==> (publish (lambda (publish-type)
+;; ==> (let* ((area-num (or (string->number (iup:attribute areas-sel "VALUE")) 0))
+;; ==> (area-dat (if (> area-num 0)(list-ref areas (- area-num 1))'("NOT SELECTED" "NOT SELECTED")))
+;; ==> (area-path (cadr area-dat))
+;; ==> (area-name (car area-dat))
+;; ==> (version (iup:attribute version-tb "VALUE"))
+;; ==> (comment (iup:attribute comment-tb "VALUE"))
+;; ==> (spath (iup:attribute source-tb "VALUE"))
+;; ==> (submitter (current-user-name))
+;; ==> (quality 2))
+;; ==> (datashare:publish configdat publish-type area-name version comment spath submitter quality))))
+;; ==> (copy (iup:button "Copy and Publish"
+;; ==> #:expand "HORIZONTAL"
+;; ==> #:action (lambda (obj)
+;; ==> (publish 'copy))))
+;; ==> (link (iup:button "Link and Publish"
+;; ==> #:expand "HORIZONTAL"
+;; ==> #:action (lambda (obj)
+;; ==> (publish 'link))))
+;; ==> (browse-btn (iup:button "Browse"
+;; ==> #:size "40x"
+;; ==> #:action (lambda (obj)
+;; ==> (let* ((fd (iup:file-dialog #:dialogtype "DIR"))
+;; ==> (top (iup:show fd #:modal? "YES")))
+;; ==> (iup:attribute-set! source-tb "VALUE"
+;; ==> (iup:attribute fd "VALUE"))
+;; ==> (iup:destroy! fd))))))
+;; ==> (print "areas")
+;; ==> ;; (pp areas)
+;; ==> (fold (lambda (areadat num)
+;; ==> ;; (print "Adding num=" num ", areadat=" areadat)
+;; ==> (iup:attribute-set! areas-sel (conc num) (car areadat))
+;; ==> (+ 1 num))
+;; ==> 1 areas)
+;; ==> (iup:vbox
+;; ==> (iup:hbox (iup:label "Area:" #:size label-size) ;; area-filter
+;; ==> areas-sel)
+;; ==> (iup:hbox (iup:label "Version:" #:size label-size) version-tb)
+;; ==> ;; (iup:hbox (iup:label "Link only" #:size label-size) copy-link)
+;; ==> ;; (iup:label "Iteration:") iteration)
+;; ==> (iup:hbox (iup:label "Comment:" #:size label-size) comment-tb)
+;; ==> (iup:hbox (iup:label "Source base path:" #:size label-size) source-tb browse-btn)
+;; ==> (iup:hbox copy link))))
+;; ==>
+;; ==> (define (datashare:lst->path pathlst)
+;; ==> (conc "/" (string-intersperse (map conc pathlst) "/")))
+;; ==>
+;; ==> (define (datashare:path->lst path)
+;; ==> (string-split path "/"))
+;; ==>
+;; ==> (define (datashare:pathdat-apply-heuristics configdat path)
+;; ==> (cond
+;; ==> ((common:file-exists? path) "found")
+;; ==> (else (conc path " not installed"))))
+;; ==>
+;; ==> (define (datashare:get-view configdat)
+;; ==> (iup:vbox
+;; ==> (iup:hbox
+;; ==> (let* ((label-size "60x")
+;; ==> ;; filter elements
+;; ==> (area-filter "%")
+;; ==> (version-filter "%")
+;; ==> (iter-filter ">= 0")
+;; ==> ;; reverse lookup from path to data for src and installed
+;; ==> (srcdat (make-hash-table)) ;; reverse lookup
+;; ==> (installed-dat (make-hash-table))
+;; ==> ;; config values
+;; ==> (basepath (configf:lookup configdat "settings" "basepath"))
+;; ==> ;; gui elements
+;; ==> (submitter (iup:label "" #:expand "HORIZONTAL"))
+;; ==> (date-submitted (iup:label "" #:expand "HORIZONTAL"))
+;; ==> (comment (iup:label "" #:expand "HORIZONTAL"))
+;; ==> (copy-link (iup:label "" #:expand "HORIZONTAL"))
+;; ==> (quality (iup:label "" #:expand "HORIZONTAL"))
+;; ==> (installed-status (iup:label "" #:expand "HORIZONTAL"))
+;; ==> ;; misc
+;; ==> (curr-record #f)
+;; ==> ;; (source-data (iup:label "" #:expand "HORIZONTAL"))
+;; ==> (tb (iup:treebox
+;; ==> #:value 0
+;; ==> #:name "Packages"
+;; ==> #:expand "YES"
+;; ==> #:addexpanded "NO"
+;; ==> #:selection-cb
+;; ==> (lambda (obj id state)
+;; ==> ;; (print "obj: " obj ", id: " id ", state: " state)
+;; ==> (let* ((path (datashare:lst->path (cdr (tree:node->path obj id))))
+;; ==> (record (hash-table-ref/default srcdat path #f)))
+;; ==> (if record
+;; ==> (begin
+;; ==> (set! curr-record record)
+;; ==> (iup:attribute-set! submitter "TITLE" (datashare:pkg-get-submitter record))
+;; ==> (iup:attribute-set! date-submitted "TITLE" (time->string (seconds->local-time (datashare:pkg-get-datetime record))))
+;; ==> (iup:attribute-set! comment "TITLE" (datashare:pkg-get-comment record))
+;; ==> (iup:attribute-set! quality "TITLE" (datashare:pkg-get-quality record))
+;; ==> (iup:attribute-set! copy-link "TITLE" (datashare:pkg-get-store_type record))
+;; ==> ))
+;; ==> ;; (print "id=" id " path=" path " record=" record);; (tree:node->path obj id) " run-id: " run-id)
+;; ==> ))))
+;; ==> (tb2 (iup:treebox
+;; ==> #:value 0
+;; ==> #:name "Installed"
+;; ==> #:expand "YES"
+;; ==> #:addexpanded "NO"
+;; ==> #:selection-cb
+;; ==> (lambda (obj id state)
+;; ==> ;; (print "obj: " obj ", id: " id ", state: " state)
+;; ==> (let* ((path (datashare:lst->path (cdr (tree:node->path obj id))))
+;; ==> (status (hash-table-ref/default installed-dat path #f)))
+;; ==> (iup:attribute-set! installed-status "TITLE" (if status status ""))
+;; ==> ))))
+;; ==> (refresh (lambda (obj)
+;; ==> (let* ((db (datashare:open-db configdat))
+;; ==> (areas (or (configf:get-section configdat "areas") '())))
+;; ==> ;;
+;; ==> ;; first update the Sources
+;; ==> ;;
+;; ==> (for-each
+;; ==> (lambda (pkgitem)
+;; ==> (let* ((pkg-path (list (datashare:pkg-get-area pkgitem)
+;; ==> (datashare:pkg-get-version_name pkgitem)
+;; ==> (datashare:pkg-get-iteration pkgitem)))
+;; ==> (pkg-id (datashare:pkg-get-id pkgitem))
+;; ==> (path (datashare:lst->path pkg-path)))
+;; ==> ;; (print "tree:add-node tb=" tb ", pkg-path=" pkg-path ", pkg-id=" pkg-id)
+;; ==> (if (not (hash-table-ref/default srcdat path #f))
+;; ==> (tree:add-node tb "Packages" pkg-path userdata: (conc "pkg-id: " pkg-id)))
+;; ==> ;; (print "path=" path " pkgitem=" pkgitem)
+;; ==> (hash-table-set! srcdat path pkgitem)))
+;; ==> (datashare:get-pkgs db area-filter version-filter iter-filter))
+;; ==> ;;
+;; ==> ;; then update the installed
+;; ==> ;;
+;; ==> (for-each
+;; ==> (lambda (area)
+;; ==> (let* ((path (conc "/" (cadr area)))
+;; ==> (fullpath (conc basepath path)))
+;; ==> (if (not (hash-table-ref/default installed-dat path #f))
+;; ==> (tree:add-node tb2 "Installed" (datashare:path->lst path)))
+;; ==> (hash-table-set! installed-dat path (datashare:pathdat-apply-heuristics configdat fullpath))))
+;; ==> areas)
+;; ==> (sqlite3:finalize! db))))
+;; ==> (apply (iup:button "Apply"
+;; ==> #:action
+;; ==> (lambda (obj)
+;; ==> (if curr-record
+;; ==> (let* ((area (datashare:pkg-get-area curr-record))
+;; ==> (stored-path (datashare:pkg-get-stored_path curr-record))
+;; ==> (source-type (datashare:pkg-get-store_type curr-record))
+;; ==> (source-path (case source-type ;; (equal? source-type "link"))
+;; ==> ((link)(datashare:pkg-get-source-path curr-record))
+;; ==> ((copy)stored-path)
+;; ==> (else #f)))
+;; ==> (dest-stub (configf:lookup configdat "areas" area))
+;; ==> (target-path (conc basepath "/" dest-stub)))
+;; ==> (datashare:build-dir-make-link stored-path target-path)
+;; ==> (print "Creating link from " stored-path " to " target-path)))))))
+;; ==> (iup:vbox
+;; ==> (iup:hbox tb tb2)
+;; ==> (iup:frame
+;; ==> #:title "Source Info"
+;; ==> (iup:vbox
+;; ==> (iup:hbox (iup:button "Refresh" #:action refresh) apply)
+;; ==> (iup:hbox (iup:label "Submitter: ") ;; #:size label-size)
+;; ==> submitter
+;; ==> (iup:label "Submitted on: ") ;; #:size label-size)
+;; ==> date-submitted)
+;; ==> (iup:hbox (iup:label "Data stored: ")
+;; ==> copy-link
+;; ==> (iup:label "Quality: ")
+;; ==> quality)
+;; ==> (iup:hbox (iup:label "Comment: ")
+;; ==> comment)))
+;; ==> (iup:frame
+;; ==> #:title "Installed Info"
+;; ==> (iup:vbox
+;; ==> (iup:hbox (iup:label "Installed status/path: ") installed-status)))
+;; ==> )))))
+;; ==>
+;; ==> (define (datashare:manage-view configdat)
+;; ==> (iup:vbox
+;; ==> (iup:hbox
+;; ==> (iup:button "Pushme"
+;; ==> #:expand "YES"
+;; ==> ))))
+;; ==>
+;; ==> (define (datashare:gui configdat)
+;; ==> (iup:show
+;; ==> (iup:dialog
+;; ==> #:title (conc "DataShare dashboard " (current-user-name) ":" (current-directory))
+;; ==> #:menu (datashare:main-menu)
+;; ==> (let* ((tabs (iup:tabs
+;; ==> #:tabchangepos-cb (lambda (obj curr prev)
+;; ==> (set! *datashare:current-tab-number* curr))
+;; ==> (datashare:publish-view configdat)
+;; ==> (datashare:get-view configdat)
+;; ==> (datashare:manage-view configdat)
+;; ==> )))
+;; ==> ;; (set! (iup:callback tabs tabchange-cb:) (lambda (a b c)(print "SWITCHED TO TAB: " a " " b " " c)))
+;; ==> (iup:attribute-set! tabs "TABTITLE0" "Publish")
+;; ==> (iup:attribute-set! tabs "TABTITLE1" "Get")
+;; ==> (iup:attribute-set! tabs "TABTITLE2" "Manage")
+;; ==> ;; (iup:attribute-set! tabs "BGCOLOR" "190 190 190")
+;; ==> tabs)))
+;; ==> (iup:main-loop))
+;; ==>
+;; ==> ;;======================================================================
+;; ==> ;; MISC
+;; ==> ;;======================================================================
+;; ==>
+;; ==>
+;; ==> (define (datashare:do-as-calling-user proc)
+;; ==> (let ((eid (current-effective-user-id))
+;; ==> (cid (current-user-id)))
+;; ==> (if (not (eq? eid cid)) ;; running suid
+;; ==> (set! (current-effective-user-id) cid))
+;; ==> ;; (print "running as " (current-effective-user-id))
+;; ==> (proc)
+;; ==> (if (not (eq? eid cid))
+;; ==> (set! (current-effective-user-id) eid))))
+;; ==>
+;; ==> (define (datashare:find name paths)
+;; ==> (if (null? paths)
+;; ==> #f
+;; ==> (let loop ((hed (car paths))
+;; ==> (tal (cdr paths)))
+;; ==> (if (common:file-exists? (conc hed "/" name))
+;; ==> hed
+;; ==> (if (null? tal)
+;; ==> #f
+;; ==> (loop (car tal)(cdr tal)))))))
+;; ==>
+;; ==> ;;======================================================================
+;; ==> ;; MAIN
+;; ==> ;;======================================================================
+;; ==>
+;; ==> (define (datashare:load-config exe-dir exe-name)
+;; ==> (let* ((fname (conc exe-dir "/." exe-name ".config")))
+;; ==> (ini:property-separator-patt " * *")
+;; ==> (ini:property-separator #\space)
+;; ==> (if (common:file-exists? fname)
+;; ==> ;; (ini:read-ini fname)
+;; ==> (read-config fname #f #t)
+;; ==> (make-hash-table))))
+;; ==>
+;; ==> (define (datashare:process-action configdat action . args)
+;; ==> (case (string->symbol action)
+;; ==> ((get)
+;; ==> (if (< (length args) 2)
+;; ==> (begin
+;; ==> (print "ERROR: Missing arguments; " (string-intersperse args ", "))
+;; ==> (exit 1))
+;; ==> (let* ((basepath (configf:lookup configdat "settings" "basepath"))
+;; ==> (db (datashare:open-db configdat))
+;; ==> (area (car args))
+;; ==> (version (cadr args)) ;; iteration
+;; ==> (remargs (args:get-args args '("-i") '() args:arg-hash 0))
+;; ==> (iteration (if (args:get-arg "-i")(string->number (args:get-arg "-i")) #f))
+;; ==> (curr-record (datashare:get-pkg db area version iteration: iteration)))
+;; ==> (if (not curr-record)
+;; ==> (begin
+;; ==> (print "ERROR: No matching record found; area=" area ", version=" version ", iteration=" (if iteration iteration "(max)"))
+;; ==> (exit 1))
+;; ==> (let* ((stored-path (datashare:pkg-get-stored_path curr-record))
+;; ==> (source-type (datashare:pkg-get-store_type curr-record))
+;; ==> (source-path (case source-type ;; (equal? source-type "link"))
+;; ==> ((link) (datashare:pkg-get-source-path curr-record))
+;; ==> ((copy) stored-path)
+;; ==> (else #f)))
+;; ==> (dest-stub (configf:lookup configdat "areas" area))
+;; ==> (target-path (conc basepath "/" dest-stub)))
+;; ==> (datashare:build-dir-make-link stored-path target-path)
+;; ==> (datashare:record-pkg-ref db (datashare:pkg-get-id curr-record) target-path)
+;; ==> (sqlite3:finalize! db)
+;; ==> (print "Creating link from " stored-path " to " target-path))))))
+;; ==> ((publish)
+;; ==> (if (< (length args) 3)
+;; ==> (begin
+;; ==> (print "ERROR: Missing arguments; " (string-intersperse args ", "))
+;; ==> (exit 1))
+;; ==> (let* ((srcpath (list-ref args 0))
+;; ==> (areaname (list-ref args 1))
+;; ==> (version (list-ref args 2))
+;; ==> (remargs (args:get-args (drop args 2)
+;; ==> '("-type" ;; link or copy (default is copy)
+;; ==> "-m")
+;; ==> '()
+;; ==> args:arg-hash
+;; ==> 0))
+;; ==> (publish-type (if (equal? (args:get-arg "-type") "link") 'link 'copy))
+;; ==> (comment (or (args:get-arg "-m") ""))
+;; ==> (submitter (current-user-name))
+;; ==> (quality (args:get-arg "-quality"))
+;; ==> (publish-res (datashare:publish configdat publish-type areaname version comment srcpath submitter quality)))
+;; ==> (if (not (car publish-res))
+;; ==> (begin
+;; ==> (print "ERROR: " (cdr publish-res))
+;; ==> (exit 1))))))
+;; ==> ((list-versions)
+;; ==> (let ((area-name (car args)) ;; version patt full print
+;; ==> (remargs (args:get-args args '("-vpatt") '("-full") args:arg-hash 0))
+;; ==> (db (datashare:open-db configdat))
+;; ==> (versions (datashare:get-versions-for-area db (car args) version-patt: (args:get-arg "-vpatt"))))
+;; ==> ;; (print "area-name=" area-name " args=" args " *args-hash*=" (hash-table->alist *args-hash*))
+;; ==> (map (lambda (x)
+;; ==> (if (args:get-arg "-full")
+;; ==> (format #t
+;; ==> "~10a~10a~4a~27a~30a\n"
+;; ==> (vector-ref x 0)
+;; ==> (vector-ref x 1)
+;; ==> (vector-ref x 2)
+;; ==> (conc "\"" (time->string (seconds->local-time (vector-ref x 3))) "\"")
+;; ==> (conc "\"" (vector-ref x 4) "\""))
+;; ==> (print (vector-ref x 0))))
+;; ==> versions)
+;; ==> (sqlite3:finalize! db)))))
+;; ==>
+;; ==> ;; ease debugging by loading ~/.dashboardrc - REMOVE FROM PRODUCTION!
+;; ==> (let ((debugcontrolf (conc (get-environment-variable "HOME") "/.datasharerc")))
+;; ==> (if (common:file-exists? debugcontrolf)
+;; ==> (load debugcontrolf)))
+;; ==>
+;; ==> (define (main)
+;; ==> (let* ((args (argv))
+;; ==> (prog (car args))
+;; ==> (rema (cdr args))
+;; ==> (exe-name (pathname-file (car (argv))))
+;; ==> (exe-dir (or (pathname-directory prog)
+;; ==> (datashare:find exe-name (string-split (get-environment-variable "PATH") ":"))))
+;; ==> (configdat (datashare:load-config exe-dir exe-name)))
+;; ==> (cond
+;; ==> ;; one-word commands
+;; ==> ((eq? (length rema) 1)
+;; ==> (case (string->symbol (car rema))
+;; ==> ((help -h -help --h --help)
+;; ==> (print datashare:help))
+;; ==> ((list-areas)
+;; ==> (map print (datashare:get-areas configdat)))
+;; ==> (else
+;; ==> (print "ERROR: Unrecognised command. Try \"datashare help\""))))
+;; ==> ;; multi-word commands
+;; ==> ((null? rema)(datashare:gui configdat))
+;; ==> ((>= (length rema) 2)
+;; ==> (apply datashare:process-action configdat (car rema)(cdr rema)))
+;; ==> (else (print "ERROR: Unrecognised command. Try \"datashare help\"")))))
+;; ==>
+;; ==> (main)