source: trunk/gui/scripts/filexfer.tcl @ 2788

Last change on this file since 2788 was 2788, checked in by gah, 12 years ago
File size: 14.2 KB
Line 
1# ----------------------------------------------------------------------
2#  COMPONENT: filexfer - support for file transfer with user's desktop
3#
4#  Supports interactions with the filexfer middleware, enabling
5#  Rappture to transfer files to and from the user's desktop.
6#  Files are downloaded by invoking "exportfile", and uploaded by
7#  invoking "importfile".  The middleware handles the rest.
8# ======================================================================
9#  AUTHOR:  Michael McLennan, Purdue University
10#  Copyright (c) 2004-2007  Purdue Research Foundation
11#
12#  See the file "license.terms" for information on usage and
13#  redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES.
14# ======================================================================
15package require BLT
16
17namespace eval Rappture { # forward declaration }
18namespace eval Rappture::filexfer {
19    variable enabled 0   ;# set to 1 when middleware is in place
20    variable commands    ;# complete path to exportfile/importfile
21    variable job         ;# used to manage bgexec jobs
22}
23
24# ----------------------------------------------------------------------
25# USAGE: Rappture::filexfer::init
26#
27# Called in the main application to see if the filexfer middleware
28# is installed.  Returns 1 if the middleware is installed, and 0
29# otherwise.
30# ----------------------------------------------------------------------
31proc Rappture::filexfer::init {} {
32    variable enabled
33    variable commands
34
35    #
36    # Look for the exportfile/importfile commands and see if
37    # they appear to be working.  If we have both, then this
38    # is "enabled".
39    #
40    foreach op {export import} {
41        set prog "${op}file"
42        set path [auto_execok $prog]
43        if {"" == $path} {
44            foreach dir {/apps/bin /apps/filexfer/bin} {
45                set p [file join $dir $prog]
46                if {[file executable $p]} {
47                    set path $p
48                    break
49                }
50            }
51        }
52        if {[file executable $path]} {
53            # the new filexfer has a --provenance arg for file names
54            set info [exec $path --help  2>@1]
55            if {[regexp -- {--provenance} $info]} {
56                lappend path --provenance
57            }
58            set commands($op) $path
59        } else {
60            return 0
61        }
62    }
63
64    return [set enabled 1]
65}
66
67# ----------------------------------------------------------------------
68# USAGE: Rappture::filexfer::enabled
69#
70# Clients use this to see if the filexfer stuff is up and running.
71# If so, then the GUI will provide "Download..." and other filexfer
72# options.  If not, then Rappture must be running within an
73# environment that doesn't support it.  As a backup, Rappture provides
74# file load/save operations instead.
75# ----------------------------------------------------------------------
76proc Rappture::filexfer::enabled {} {
77    variable enabled
78    return $enabled
79}
80
81# ----------------------------------------------------------------------
82# USAGE: Rappture::filexfer::label upload|download
83#
84# Clients use this to query the proper label for an "upload" or
85# "download" opertion.  These are usually "Upload..." and "Download",
86# but could change to "Load File..." and "Save As..." for applications
87# in desktop mode.
88# ----------------------------------------------------------------------
89proc Rappture::filexfer::label {operation} {
90    switch -- $operation {
91        upload {
92            if {[Rappture::filexfer::enabled]} {
93                return "Upload..."
94            } else {
95                return "Load File..."
96            }
97        }
98        uploadWord {
99            if {[Rappture::filexfer::enabled]} {
100                return "Upload"
101            } else {
102                return "Browse"
103            }
104        }
105        download {
106            if {[Rappture::filexfer::enabled]} {
107                return "Download"
108            } else {
109                return "Save As..."
110            }
111        }
112        downloadWord {
113            if {[Rappture::filexfer::enabled]} {
114                return "Download"
115            } else {
116                return "Save"
117            }
118        }
119        default {
120            error "bad option \"$operation\": should be upload, uploadWord, download, or downloadWord"
121        }
122    }
123}
124
125
126# ----------------------------------------------------------------------
127# USAGE: Rappture::filexfer::upload <toolName> <controlList> <callback>
128#
129# Clients use this to prompt the user to upload a file.  The string
130# <toolName> is used to identify the application within the web form.
131# The <controlList> is a list of controls that could be uploaded:
132#
133#   { <id1> <label1> <desc1>  <id2> <label2> <desc2> ... }
134#
135# The user is prompted for each of the controls in <controlList>.
136# If successful, the <callback> is invoked to handle the uploaded
137# information.  If anything goes wrong, the same callback is used
138# to post errors near the widget that invoked this operation.
139#
140# The <id> is usually a Rappture path for the control that is being
141# uploaded into.  It is passed along to the <callback>, so that code
142# knows where to put the result.  If <id> is "@@<dir>", then the file
143# is uploaded into the specified directory <dir> using the name that
144# comes from the remote upload.
145# ----------------------------------------------------------------------
146proc Rappture::filexfer::upload {tool controlList callback} {
147    global env
148    variable enabled
149    variable commands
150    variable job
151
152    if {$enabled} {
153        set cmd $commands(import)
154        if {"" != $tool} {
155            lappend cmd --for "for $tool"
156        }
157
158        set dir ~/data/sessions/$env(SESSION)/spool
159        if {![file exists $dir]} {
160            catch {file mkdir $dir}
161        }
162
163        set i 0
164        foreach {path label desc} $controlList {
165            set file [file join $dir upload[pid]-[incr i]]
166            set file2path($file) $path
167            set file2label($file) $label
168            lappend cmd --label $label $file
169        }
170
171        uplevel #0 [list $callback error "Upload starting...\nA web browser page should pop up on your desktop.  Use that form to handle the upload operation.\n\nIf the upload form doesn't pop up, make sure that you're allowing pop ups from this site.  If it still doesn't pop up, you may be having trouble with the version of Java installed for your browser.  See our Support area for details.\n\nClick anywhere to dismiss this message."]
172
173        set job(output) ""
174        set job(error) ""
175
176        set status [catch {eval \
177            blt::bgexec ::Rappture::filexfer::job(control) \
178              -output ::Rappture::filexfer::job(output) \
179              -error ::Rappture::filexfer::job(error) \
180              $cmd
181        } result]
182
183        if {$status == 0} {
184            set changed ""
185            set errs ""
186            foreach line [split $job(output) \n] {
187                # the new filexfer reports "file <= desktopName"
188                # the old one just reports "file"
189                set remote ""
190                if {![regexp {(.+) +<= +(.+)} $line match file remote]} {
191                    set file [string trim $line]
192                }
193
194                # load the uploaded for this control
195                set status [catch {
196                    set fid [open $file r]
197                    fconfigure $fid -translation binary -encoding binary
198                    set info [read $fid]
199                    close $fid
200                    if {[string match @@* $file2path($file)]} {
201                        if {$remote ne "" && $remote ne "@CLIPBOARD"} {
202                            set dir [string range $file2path($file) 2 end]
203                            if {$dir eq ""} { set dir [pwd] }
204                            set newloc [file join $dir $remote]
205                            file rename -force -- $file $newloc
206                        }
207                    } else {
208                        file delete -force $file
209                    }
210                } result]
211
212                if {$status != 0} {
213                    append errs "Error loading data for \"$file2label($file)\":\n$result\n"
214                } else {
215                    lappend changed $file2path($file)
216                    set cmd [list $callback path $file2path($file) local $file data $info]
217                    if {$remote ne ""} {
218                        lappend cmd remote $remote
219                    }
220                    uplevel #0 $cmd
221                }
222            }
223            if {[llength $changed] == 0} {
224                set errs "The form was empty, so none of your controls were changed.  In order to upload a file, you must select the file name--or enter text into the copy/paste area--before pushing the Upload button."
225            }
226            if {[string length $errs] > 0} {
227                uplevel #0 [list $callback error $errs]
228            }
229        } else {
230            uplevel #0 [list $callback error $job(error)]
231        }
232    } else {
233        #
234        # Filexfer via importfile is disabled.  This tool must be
235        # running in a desktop environment.  Instead of uploading,
236        # perform a "Load File..." operation with a standard file
237        # selection dialog.
238        #
239        set i 0
240        foreach {path label desc} $controlList {
241            set file [tk_getOpenFile -title "Load File: $label"]
242            if {"" != $file} {
243                set cmds {
244                    # try to read first as binary
245                    set fid [open $file r]
246                    fconfigure $fid -encoding binary -translation binary
247                    set info [read $fid]
248                    close $fid
249                    if {![Rappture::encoding::is binary $info]} {
250                        # not binary? then re-read and translate CR/LF
251                        set fid [open $file r]
252                        set info [read $fid]
253                        close $fid
254                    }
255                }
256                if {[catch $cmds err]} {
257                    uplevel #0 [list $callback error "Error loading file [file tail $file]: $err"]
258                } else {
259                    uplevel #0 [list $callback path $path data $info filename $file]
260                }
261            }
262        }
263    }
264}
265
266# ----------------------------------------------------------------------
267# USAGE: Rappture::filexfer::download <string> ?<filename>?
268#
269# Clients use this to send a file off to the user.  The <string>
270# is stored in a file called <filename> in the user's spool directory.
271# If there is already a file by that name, then the name is modified
272# to make it unique.  Once the string has been stored in the file,
273# a message is sent to all clients listening, letting them know
274# that the file is available.
275#
276# If anything goes wrong, this function returns a string that should
277# be displayed to the user to explain the problem.
278# ----------------------------------------------------------------------
279proc Rappture::filexfer::download {string {filename "output.txt"}} {
280    global env
281    variable enabled
282    variable commands
283    variable job
284
285    if {$enabled} {
286        # make a spool directory, if we don't have one already
287        set dir ~/data/sessions/$env(SESSION)/spool
288        if {![file exists $dir]} {
289            catch {file mkdir $dir}
290        }
291
292        if {[file exists [file join $dir $filename]]} {
293            #
294            # Find a similar file name that doesn't conflict
295            # with an existing file:  e.g., output2.txt
296            #
297            set root [file rootname $filename]
298            set ext [file extension $filename]
299            set counter 2
300            while {1} {
301                set filename "$root$counter$ext"
302                if {![file exists [file join $dir $filename]]} {
303                    break
304                }
305                incr counter
306            }
307        }
308
309        #
310        # Save the file in the spool directory, then have it
311        # exported.
312        #
313        if {[catch {
314            set file [file join $dir $filename]
315            set fid [open $file w]
316            fconfigure $fid -encoding binary -translation binary
317            puts -nonewline $fid $string
318            close $fid
319        } result]} {
320            return $result
321        }
322
323        set job(output) ""
324        set job(error) ""
325
326        set status [catch {blt::bgexec ::Rappture::filexfer::job(control) \
327            -output ::Rappture::filexfer::job(output) \
328            -error ::Rappture::filexfer::job(error) \
329            $commands(export) --timeout 300 --delete $file} result]
330
331        if {$status != 0} {
332            return $Rappture::filexfer::job(error)
333        }
334    } else {
335        #
336        # Filexfer via exportfile is disabled.  This tool must be
337        # running in a desktop environment.  Instead of downloading,
338        # perform a "Save As..." operation with a standard file
339        # selection dialog.
340        #
341        set file [tk_getSaveFile -title "Save As..." -initialfile $filename]
342        # FIXME: Why is there a grab still pending?
343        set grab [grab current]
344        if { $grab != "" } {
345            grab release $grab
346        }
347        if {"" != $file} {
348            if {[catch {
349                set fid [open $file w]
350                if {[Rappture::encoding::is binary $string]} {
351                    fconfigure $fid -encoding binary -translation binary
352                    puts -nonewline $fid $string
353                } else {
354                    puts $fid $string
355                }
356                close $fid
357            } result]} {
358                return $result
359            }
360        }
361    }
362    return ""
363}
364
365# ----------------------------------------------------------------------
366# USAGE: Rappture::filexfer::webpage <url>
367#
368# Clients use this to pop up a web page for the specified <url> on
369# the user's desktop.  The <url> should start with http:// or https://.
370# If anything goes wrong, this function beeps.
371# ----------------------------------------------------------------------
372proc Rappture::filexfer::webpage {url} {
373    if {[regexp -nocase {^https?://} $url]} {
374        foreach prog {
375              clientaction
376              /usr/lib/hubzero/clientaction
377              /apps/bin/clientaction
378              /apps/xvnc/bin/clientaction
379              /usr/lib/mw/bin/clientaction
380              ""
381        } {
382            if {"" != [auto_execok $prog]} {
383                break
384            }
385        }
386        if {"" != $prog} {
387            exec $prog url $url &
388            return
389        }
390    }
391    bell
392}
Note: See TracBrowser for help on using the repository browser.