~ [ source navigation ] ~ [ diff markup ] ~ [ identifier search ] ~ [ freetext search ] ~ [ file search ] ~

Open Mash Cross Reference
mash/tcl/indiva/imgr/indiva-manager.tcl

Component: ~ [ mash ] ~ [ apps ] ~ [ gsm ] ~ [ lib ] ~ [ otcl ] ~ [ srm ] ~ [ tcl8.3 ] ~ [ tclcl ] ~ [ tk8.3 ] ~ [ tutorials ] ~

  1 # indiva-manager.tcl --
  2 #
  3 #   Implementation of the Indiva Manager RPC API.
  4 #
  5 # Copyright (c) 1996-2002 The Regents of the University of California.
  6 # All rights reserved.
  7 #
  8 # Redistribution and use in source and binary forms, with or without
  9 # modification, are permitted provided that the following conditions are met:
 10 #
 11 # A. Redistributions of source code must retain the above copyright notice,
 12 #    this list of conditions and the following disclaimer.
 13 # B. Redistributions in binary form must reproduce the above copyright notice,
 14 #    this list of conditions and the following disclaimer in the documentation
 15 #    and/or other materials provided with the distribution.
 16 # C. Neither the names of the copyright holders nor the names of its
 17 #    contributors may be used to endorse or promote products derived from this
 18 #    software without specific prior written permission.
 19 #
 20 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS''
 21 # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 22 # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 23 # ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR
 24 # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 25 # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 26 # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 27 # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 28 # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 29 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 30 #
 31 # @(#) $Header: /usr/mash/src/repository/mash/mash-1/tcl/indiva/imgr/indiva-manager.tcl,v 1.16 2002/08/27 02:56:19 weitsang Exp $
 32 
 33 import Dp
 34 import RTP
 35 import IMgrHttpd
 36 import MobGraph
 37 import Capability/Switchable
 38 import SDPParser
 39 import IMgrServent
 40 import IMgrRTCPListener
 41 
 42 #--------------------------------------------------------------------------
 43 # Class:
 44 #   IndivaManager
 45 #
 46 # Description:
 47 #   The IndivaManager object sits inside the infrastructure and 
 48 #   serves as the bridge between applications and the distributed
 49 #   media environment.
 50 #
 51 #   IndivaManager maintains a mob graph, which represents the connection 
 52 #   and routing information between mobs.  
 53 #
 54 #   IndivaManager also maintains a mob directory tree, which mirrors
 55 #   a real directory structure on disk.  Part of the directory tree
 56 #   rarely changes within a domain (such as devices), while others
 57 #   changes continuously (such as conferences).
 58 # 
 59 #   There can be only one IndivaManager per Indiva Server.
 60 #
 61 #  Members:
 62 #    root_ -- the default root to the file directory for storing mobs.
 63 #--------------------------------------------------------------------------
 64 Class IndivaManager
 65 
 66 #--------------------------------------------------------------------------
 67 # Method:
 68 #   IndivaManager create
 69 # Description:
 70 #   Overrides Class create method, so that we make sure only one instance
 71 #   of IndivaManager is available at one time.
 72 #--------------------------------------------------------------------------
 73 IndivaManager proc create { args } {
 74     $self instvar instance_ 
 75     if {![info exists instance_]} {
 76         set instance_ [eval Object create $args]
 77         $instance_ class IndivaManager
 78         $instance_ init
 79     }
 80     return $instance_
 81 }
 82 
 83 
 84 #--------------------------------------------------------------------------
 85 # Method:
 86 #   IndivaManager init
 87 # Description:
 88 #   Read mobs from $root_ and initialize the a/v graph.
 89 #   Create an RPC server to listen to client request.
 90 #--------------------------------------------------------------------------
 91 IndivaManager public init { args } {
 92 
 93     # Initialize root_
 94     $self instvar root_ port_ rtp_
 95     set root_ [$self get_option root]
 96     set port_ [$self get_option port]
 97 
 98     # Load all mobs, and create the mob graph.  
 99     $self instvar mob_graph_
100     set mob_graph_ [new MobGraph $root_]
101 
102     # RTP is used to map format code into format name (e.g 31 -> h261)
103     set rtp_ [new RTP]
104 
105     # Start RPC server
106     if {[catch {dp_MakeRPCServer $port_ \
107             "$self on_login" none [list [list $self on_close]]} err]} {
108         error "Unable to create RPC Server: $err"
109     } else {
110         MashLog info "Indiva manager started"
111     }
112 
113     set httpd [new IMgrHttpd $self $root_ [expr $port_ + 1]] 
114 }
115 
116 
117 #--------------------------------------------------------------------------
118 # Method:
119 #   IndivaManager exists
120 # Description:
121 #   This is an RPC call from clients to the server.  It returns true
122 #   if $mob exists, and false otherwise.
123 # Arguments:
124 #   mob -- the mob to check.
125 #--------------------------------------------------------------------------
126 IndivaManager public exists { mob } {
127     set mob [$self map_to_local_name $mob]
128     return [file exists $mob]
129 }
130 
131 
132 #--------------------------------------------------------------------------
133 # Method:
134 #   IndivaManager isdir
135 # Description:
136 #   This is an RPC call from clients to the server.  It returns true
137 #   if $mob is a directory, and false otherwise.
138 # Arguments:
139 #   mob -- the mob to check.
140 #--------------------------------------------------------------------------
141 IndivaManager public isdir { mob } {
142     set mob [$self map_to_local_name $mob]
143     return [file isdirectory $mob]
144 }
145 
146 
147 #--------------------------------------------------------------------------
148 # Method:
149 #   IndivaManager ls
150 # Description:
151 #   This is an RPC call from clients to the server.  The current list of
152 #   mobs under $dir is returned.  If $mob does not exist in the INDIVA 
153 #   name space, an error is returned.  If $mob is a file, then we simply 
154 #   return $mob.
155 # Arguments:
156 #   mob -- Absolute and normalized path to the directory or file to list. 
157 #          Can contain glob style regular expression.
158 #--------------------------------------------------------------------------
159 IndivaManager public ls {mob}  {
160     set lname [$self map_to_local_name $mob]
161     set matches [lsort [glob -nocomplain $lname]]
162     if {$matches == ""} {
163         error "ls: no object matches $mob."
164     }
165     if {$matches == $lname} {
166         # $lname is not a regular expression.
167         if [file isdirectory $lname] {
168             return [exec ls $lname]
169         } else {
170             return $mob
171         }
172     } else {
173         set content $matches
174         set result ""
175         foreach m $content {
176             lappend result [$self map_to_remote_name $mob $m]
177         }
178         return $result
179     }
180 }
181 
182 
183 #--------------------------------------------------------------------------
184 # Method:
185 #   IndivaManager ln
186 # Description:
187 #   This is an RPC call from clients to the server.  It creates a softlink
188 #   of $mob1 in $mob2
189 #--------------------------------------------------------------------------
190 IndivaManager public ln { mob1 mob2 } {
191     set lname1 [$self map_to_local_name $mob1]
192     set lname2 [$self map_to_local_name $mob2]
193     if ![file exists $lname1] {
194         error "ln: No such object $mob1."
195     }
196 
197     if {[string match *rtp $lname1] && [string match *ses $lname2]} {
198         # Special case: if source is an rtp object and destination is a session,
199         # a link means create another copy of the stream with the same
200         # attributes.
201         set lname1 [$self resolve_link $lname1]
202         set lname2 [$self resolve_link $lname2]
203         $self instvar mob_graph_
204         set node [$mob_graph_ get_node $lname1]
205         set flow [$node flow]
206         global dp_rpcFile
207         MashClock start
208         if {$flow != ""} {
209             set newflow [$self split_flow  $dp_rpcFile $flow $lname2]
210             puts "split_flow [MashClock stop]"
211         } else {
212             set newflow [$self create_flow $dp_rpcFile $lname1 $lname2]
213             puts "create_flow [MashClock stop]"
214         }
215         return [$self map_to_remote_name $mob1 [lindex [$newflow path] end]] 
216     } else {
217         return [exec ln -s $lname1 $lname2]
218     }
219 }
220 
221 
222 #--------------------------------------------------------------------------
223 # Method:
224 #   IndivaManager rm
225 # Description:
226 #   rm is an RPC call from clients to the server to remove a mob. 
227 # Arguments:
228 #   mob -- mob is the absolute remote/local path of the mob to remove. 
229 #--------------------------------------------------------------------------
230 IndivaManager public rm { mob } {
231     set mob [$self map_to_local_name $mob]
232     if {[file type $mob] == "link"} {
233         MashLog info "rm: Removing link $mob"
234         return [exec rm $mob]
235     }
236     if ![file exists $mob] {
237         error "rm: No such object $mob."
238     }
239 
240     $self instvar mob_graph_
241     if {[string match "*.rtp" $mob] || [string match "*.srv" $mob]} {
242         set mob_node [$mob_graph_ get_node $mob]
243         delete $mob_node
244         # del_node delete edges as well, which may be needed in one 
245         # of the destroy callback.  So we should call del_node after
246         # we delete the mob.
247         $mob_graph_ del_node $mob
248     } elseif {[string match "*.con" $mob] || [string match "*.ses" $mob]} {
249         # recursively delete children, then delete self.
250         set children [glob -nocomplain $mob/*]
251         foreach child $children {
252             $self rm $child
253         }
254         set mob_node [$mob_graph_ get_node $mob]
255         delete $mob_node
256         # del_node delete edges as well, which may be needed in one 
257         # of the destroy callback.  So we should call del_node after
258         # we delete the mob.
259         $mob_graph_ del_node $mob
260     } else {
261         error "rm: unable to remove mob $mob"
262     }
263 }
264 
265 
266 #--------------------------------------------------------------------------
267 # Method:
268 #   IndivaManager mv
269 # Description:
270 #   This is an RPC call from clients to the server.  If $mob2 is a directory,
271 #   we move $mob1 from its current directory to $mob2.  Otherwise, $mob1 is
272 #   renamed to $mob2.
273 #--------------------------------------------------------------------------
274 IndivaManager public mv { mob1 mob2 } {
275     MashLog info "mv [short_name $mob1 $mob2]"
276     global dp_rpcFile
277     if ![info exists dp_rpcFile] {
278         error "mv must be called through RPC"
279     }
280 
281     set lname1 [$self map_to_local_name $mob1]
282     set lname2 [$self map_to_local_name $mob2]
283     set lname1 [$self resolve_link $lname1]
284     set lname2 [$self resolve_link $lname2]
285     if ![file exists $lname1] {
286         error "ln: No such object $mob1."
287     }
288 
289     # If source is a link, we just rename the link to the new name or move 
290     # it into a new directory.
291     if {[file type $lname1] == "link" || [file type $lname1] == "directory"} {
292         file rename $lname1 $lname2
293         return [$self map_to_remote_name $mob2 $lname2]
294     }
295 
296     $self instvar mob_graph_
297     set src_node [$mob_graph_ get_node $lname1]
298 
299     if {![$src_node is_instance_of MobStream] &&
300         ![$src_node is_instance_of MobSession] &&
301         ![$src_node is_instance_of MobConference]} {
302             error "You can only move media resources (streams, sessions, conferences) and links." 
303     }
304 
305     # If $src_node is a stream, we find out the source $S of $mob1, 
306     # and establish a new flow from $S to $mob2.
307     if {[$src_node is_instance_of MobStream]} {
308         set path [$src_node path]
309         set flow [$src_node flow]
310 
311         if {$flow == ""} {
312             error "unable to mv external source $mob1."
313         }
314         $self move_flow $dp_rpcFile $flow $lname2 
315 
316         # Now we remove the old mob, but first, we make sure the on_destroy
317         # callback is not triggered, otherwise it will remove the flow!
318         # This is a hack to get around a flawed design.  My bad.
319         $src_node on_destroy set ""
320         delete $src_node
321 
322         set p [$flow path] 
323         return [$self map_to_remote_name $mob1 [lindex $p end]]
324     }
325 }
326 
327 
328 #--------------------------------------------------------------------------
329 # Method:
330 #   IndivaManager info
331 # Description:
332 #   This is an RPC call from clients to the server.  It returns 
333 #   information about the mob called $mob_name.
334 #--------------------------------------------------------------------------
335 IndivaManager public info { mob_name args } {
336     if {[llength $args] == 1} {
337         set args [lindex $args 0]
338     }
339 
340     switch $mob_name {
341         service {
342             return [eval $self service_info $args]
343         }
344     }
345     # If mob_name consists of a single word, this is most likely a call
346     # to OTcl info method.  We call [Object info ..] and return.
347     if {[llength [split $mob_name /]] == 1} {
348         return [eval $self next $mob_name $args] 
349     }
350 
351     # Now, map $mob_name to a local name.
352     set local_name [$self map_to_local_name $mob_name]
353     set real_name [$self resolve_link $local_name]
354 
355     # If the file does not exists, just return.
356     if ![file exists $real_name] {
357         MashLog warn "WARNING: $real_name does not exists."
358         return ""
359     }
360 
361     # If the names are different after resolving link, we add the name
362     # of the link to result.
363     if {$real_name != $mob_name} {
364         append result "link {[$self map_to_remote_name $mob_name $real_name]}\n"
365     }
366 
367     # Now retrieve the mob from the graph.  If it does not exist in
368     # the graph, we return an empty string.
369     $self instvar mob_graph_
370     set mob_node [$mob_graph_ get_node $real_name]
371     if {$mob_node == ""} {
372         return ""
373     }
374 
375     # If user does not specify any additional arguments, we return
376     # all attributes.  If some arguments are specified, we return
377     # what they asked.  Reset "link" attribute where applicable.
378     if {$args == ""} {
379         append result [$mob_node get_attributes]
380     } else {
381         # If user asked for specific attributes.  Only return what they
382         # asked.  Reset "link" attribute where applicable.
383         set result {}
384         set values {}
385         foreach attr $args {
386             if {[string index $attr 0] != "-"} {
387                 return -code error "Malformed args: keys must be prefixed with -"
388             }
389             set key [string range $attr 1 end]
390             switch $key {
391                 link {
392                     lappend values [$self map_to_remote_name $mob_name $real_name]
393                 }
394                 device {
395                     if [$mob_node is_instance_of MobPort] {
396                         lappend values [$self map_to_remote_name $mob_name [[$mob_node device] name]]
397                     } else {
398                         lappend values [$self map_to_remote_name $mob_name [$mob_node name]]
399                     }
400                 }
401                 default {
402                     lappend values [$mob_node get_attribute $key]
403                 }
404             }
405         }
406         append result $values
407     }
408     return $result
409 }
410 
411 #--------------------------------------------------------------------------
412 # Method:
413 #   IndivaManager service_info
414 # Description:
415 #   This is called by info whenever a client requested info about
416 #   a service.  Based on the arguments, it returns the requested 
417 #   information about the service between a pair of mobs $from and
418 #   $to.
419 # Arguments:
420 #   from, to -- name of two mobs.
421 #--------------------------------------------------------------------------
422 IndivaManager private service_info {from to args} {
423     $self instvar mob_graph_
424     if {[catch {$mob_graph_ get_edge $from $to} edge]} {
425         set o [lindex [$mob_graph_ get_out_neighbors $from] 0]
426         if {[MobHub is_hub $o]} {
427             set edge [$mob_graph_ get_edge $from $o]
428         } else {
429             return ""
430         }
431     }
432     if {[$edge action] == "none"} {
433         return ""
434     }
435 
436     set service [$self get_service $from $to]
437     if {$service == ""} {
438         return ""
439     }
440     switch -- $args {
441         -friendlyname {
442             return [$service friendlyname]
443         }
444     }
445 }
446 
447 
448 #--------------------------------------------------------------------------
449 # Method:
450 #   IndivaManager mkdir
451 # Description:
452 #   This is an RPC call from clients to the server.  It creates a new 
453 #   directory called $name.
454 #--------------------------------------------------------------------------
455 IndivaManager public mkdir { name args } {
456     set name [$self map_to_local_name $name]
457     return [exec mkdir $name]
458 }
459 
460 
461 #--------------------------------------------------------------------------
462 # Method:
463 #   IndivaManager mkcon
464 # Description:
465 #   This is an RPC call from clients to the server.  It creates a new 
466 #   Conference directory called $name, with arguments specified by $args.
467 #--------------------------------------------------------------------------
468 IndivaManager public mkcon { conference args } {
469     set args [join $args]
470 
471     # Map $dir to the file directory.
472     set name [$self map_to_local_name $conference]
473 
474     set pos [lsearch $args "-sdp"]
475     if {$pos != -1} {
476         incr pos
477         set msg [lindex $args $pos]
478         set sdp_parser [new SDPParser 0]
479         set sdp_message [$sdp_parser parse $msg]
480         if {$sdp_message == ""} {
481             error "Error in parsing SDP message: [$sdp_parser parse_error]"
482         } else {
483             lappend args -username [$self escape [$sdp_message get creator_]]
484             lappend args -session_id [$self escape [$sdp_message get createtime_]]
485             lappend args -version [$self escape [$sdp_message get modtime_]]
486             lappend args -network_type [$self escape [$sdp_message get nettype_]]
487             lappend args -address_type [$self escape [$sdp_message get addrtype_]]
488             lappend args -creator_address [$self escape [$sdp_message get createaddr_]]
489             lappend args -name [$self escape [$sdp_message get session_name_]]
490             lappend args -description [$self escape [$sdp_message get session_info_]]
491             lappend args -phone [$self escape [$sdp_message get phonelist_]]
492             lappend args -email [$self escape [$sdp_message get emaillist_]]
493             lappend args -uri [$self escape [$sdp_message get uri_]]
494             lappend args -addr [$self escape [$sdp_message get caddr_]]
495             lappend args -bw [$self escape [$sdp_message get bwval_]:[$sdp_message get bwmod_]]
496             lappend args -timezone [$self escape [$sdp_message get zoneinfo_]]
497             lappend args -encrypt [$self escape [$sdp_message get crypt_method_]]
498             #lappend args -attributes [$sdp_message attributes]
499             foreach attr [$sdp_message attributes] {
500                 lappend args -$attr [$self escape [$sdp_message attr_value $attr]]
501             }
502         }
503         # Remove sdp from the args, and continue from there.
504         set args [lreplace $args [expr {$pos - 1}] $pos]
505         delete $sdp_parser
506     }
507 
508     if {[catch {eval new MobConference $name $args} mob]} {
509         error "unable to create conference: $mob"
510     }
511     $mob write_info_file
512     
513     $self instvar mob_graph_
514     $mob_graph_ add_conference $mob
515 
516     # Now create session if sdp is specified.
517     if {$pos != -1} {
518         $self instvar rtp_
519         foreach sdp_media [$sdp_message all_media] {
520             set args {}
521             lappend args -port [$sdp_media get port_]
522             lappend args -fmt  [$rtp_ rtp_type [$sdp_media get fmt_]]
523             set caddr [split [$sdp_media get caddr_] /]
524             lappend args -addr [lindex $caddr 0]
525             lappend args -ttl  [lindex $caddr 1]
526             lappend args -bps  [$sdp_media get bwval_]
527             lappend args -type [$sdp_media get mediatype_]
528             
529             set sesname [lindex $caddr 0]:[$sdp_media get port_]:[$sdp_media get mediatype_]
530             regsub -all "/" $sesname "-" sesname
531             $self mkses [$self map_to_remote_name $conference [$mob name]]/$sesname $args
532         }
533     }
534 }
535 
536 
537 #--------------------------------------------------------------------------
538 # Method:
539 #   IndivaManager mkses
540 # Description:
541 #   This is an RPC call from clients to the server.  It creates a new 
542 #   Session directory called $name, with arguments specified by $args.
543 #--------------------------------------------------------------------------
544 IndivaManager public mkses { name args } {
545     # Map $dir to the file directory.
546     set name [$self map_to_local_name $name]
547     set args [join $args]
548     if {[catch {eval new MobSession $name $args} mob]} {
549         error "unable to create session: $mob"
550     }
551     $mob write_info_file
552     
553     $self instvar mob_graph_
554     $mob_graph_ add_session $mob
555 
556     set name [$mob name]
557     $self instvar rtcp_listeners_
558     set rtcp_listeners_($name) [new IMgrRTCPListener $self $name]
559     $mob on_destroy append "
560         delete $rtcp_listeners_($name)
561         $self unset rtcp_listeners_($name)
562     "
563 }
564 
565 
566 #--------------------------------------------------------------------------
567 # Method:
568 #   IndivaManager mkrtp
569 # Description:
570 #   This is an RPC call from service to the server.  It creates a new 
571 #   representation of an rtp stream in the namespace.  We derive the 
572 #   directory to create the stream based on the given service_id.
573 #   Return an empty string if file already exists.
574 # Arguments:
575 #   service_id -- unique id the identify the caller.
576 #   name -- name of the rtp stream.
577 #--------------------------------------------------------------------------
578 IndivaManager public mkrtp { name args } {
579     
580     $self instvar mob_graph_
581 
582     # If this stream already exists, just return the name of the stream.
583     # Why would this happen? Becoz, we may have two different flows that
584     # end up in the same stream (think mixer).
585     if ![file exists $name] {
586         append name2 .rtp
587         if [file exists $name2] {
588             eval $self configure $name $args
589             return ""
590         }
591     } else {
592         MashLog info "mkrtp: [short_name $name] already exists"
593         eval $self configure $name $args
594         return ""
595     }
596 
597     # Stream does not exist.  Create it.
598     if {[catch {eval new MobStream $name $args} mob]} {
599         MashLog warn "mkrtp: unable to create stream: $mob"
600         return -code error "unable to create stream: $mob"
601     }
602     $mob write_info_file
603     
604     $mob_graph_ add_stream $mob
605 
606     return [$mob name]
607 }
608 
609 
610 #--------------------------------------------------------------------------
611 # Method:
612 #   IndivaManager mkserv
613 # Description:
614 #   This is a private call that creates a new representation of a service 
615 #   in the namespace.  Service will be created under directory specified
616 #   by [$self get_option servdir]
617 # Arguments:
618 #   service_id -- unique id the identify the service.
619 #   control -- control object of the service.
620 #--------------------------------------------------------------------------
621 IndivaManager public mkserv { service_id args } {
622     
623     $self instvar root_
624     set name [file join $root_ [$self get_option servdir] $service_id]
625 
626     $self instvar mob_graph_
627 
628     # Stream does not exist.  Create it.
629     if {[catch {eval new MobService $name $args} mob]} {
630         MashLog warn "WARNING: unable to create service: $mob"
631         return -code error "unable to create service: $mob"
632     }
633     $mob write_info_file
634     $mob_graph_ add_node [$mob name] $mob
635 
636     return [$mob name]
637 }
638 
639 
640 
641 #--------------------------------------------------------------------------
642 # Method:
643 #   IndivaManager encode
644 # Description:
645 #   This is an RPC call from clients to the server.  It encodes the mob
646 #   called $src_name into a stream and put it into $dest_name.
647 #--------------------------------------------------------------------------
648 IndivaManager public encode { src dest {arguments ""} } {
649     MashLog info "encode: $src $dest"
650     global dp_rpcFile
651     if {![info exists dp_rpcFile]} {
652         error "encode must be called through an RPC"
653     }
654     set client $dp_rpcFile
655 
656     $self instvar mob_graph_
657     set local_name  [$self map_to_local_name $src]
658     if {![file exists $local_name]} {
659         error "encode: No such object $src"
660     }
661     set src_name  [$self resolve_link $local_name]
662 
663     # If $src is a directory, then we encode each of its content.
664     # If the content contains a mix of signal types (video and 
665     # audio), then dest must be a list containing one video and 
666     # one audio session.
667 
668     set dest_nodes {}
669     foreach dest_name $dest {
670         set dest_name [$self map_to_local_name $dest_name]
671         set dest_name [$self resolve_link $dest_name]
672         set d [$mob_graph_ get_node $dest_name] 
673         if {$d != ""} {
674             if {[$d is_instance_of MobConference]} {
675                 # if dest is a conference, we get the children sessions
676                 # and add them to dest_nodes.
677                 foreach s [$d get_children] {
678                     lappend dest_nodes $s
679                 }
680             } else {
681                 lappend dest_nodes $d
682             }
683         } else {
684             MashLog warn "Invalid dest $dest_name. Ignore"
685         }
686     }
687 
688     if [file isdirectory $src_name] {
689         # If src is a directory, we first check if it has a default
690         # output port defined.  If it does, use it.  Otherwise, encode
691         # all output ports contained under the directory.
692         set src_node [$mob_graph_ get_node $src_name]
693         set src_nodes ""
694 
695         set defaultout [$src_node get_local_attribute "defaultout"]
696         if {$defaultout != ""} {
697             foreach s $defaultout {
698                 set s [$mob_graph_ get_node $src_name/$s]
699                 if {$s != ""} {
700                     lappend src_nodes $s
701                 }
702             }
703         } else {
704             set src_nodes [$src_node get_children]
705             if {$src_nodes == ""} {
706                 # Nothing to encode.
707                 error "Nothing to encode in $src."
708             }
709         }
710     } else {
711         set src_nodes [$mob_graph_ get_node $src_name]
712     }
713 
714     # src_nodes & dest_nodes now contain list of src and dest objects.
715     # Now, perform matching to ensure that the type are compatible.
716 
717     foreach s $src_nodes {
718         # Make sure that there is exactly 1 compatible destination for this
719         if {$s == ""} {
720             continue
721         }
722         set s_type [$s get_attribute type]
723         if {$s_type == "composite" || $s_type == "svhs" || $s_type ==
724             "s-video" || $s_type == "television"} {
725             set s_type "video"
726         }
727         set dests($s) ""
728         foreach d $dest_nodes {
729             if {[$d get_attribute type] == $s_type} {
730                 # Found a matching type
731                 if {$dests($s) != ""} {
732                     # But not the first one!
733                     error "Found more than one possible destination for [$s name]. Please be more precise."
734                 } else {
735                     set dests($s) $d
736                 }
737             }
738         }
739         if {$dests($s) == ""} {
740             error "No matching destination was found for [$s name]"
741         }
742     }
743 
744     # Now that we found the destination, we can create the flow.
745     set result {}
746     foreach s $src_nodes {
747         if {$s == ""} { continue }
748         set flow($s) [$self create_flow $client [$s name] [$dests($s) name] $arguments]
749 
750         set p [$flow($s) path] 
751         lappend result [$self map_to_remote_name $src [lindex $p end]]
752     }
753     return $result
754 }
755 
756 
757 #--------------------------------------------------------------------------
758 # Method:
759 #   IndivaManager path
760 # Description:
761 #   This is an RPC call from clients to the server.  It returns the current
762 #   path that leads to the destination mob $mob.  If $mob is not a destination,
763 #   of some services, then an empty string is returned.  
764 #   If "-device" is given as options, individual input and output ports 
765 #   in the path are replaced with name of devices.  Furthermore, consecutive 
766 #   duplicate devices and hubs are removed.
767 # Arguments:
768 #   mob -- the media object, which we wish to query the path.
769 #   args -- may contain optional arguments "-device".
770 #--------------------------------------------------------------------------
771 IndivaManager public path { mob args } {
772     $self instvar mob_graph_
773     set mob_name  [$self map_to_local_name $mob]
774     set mob_name  [$self resolve_link $mob_name]
775     set node [$mob_graph_ get_node $mob_name]
776     if {$node == ""} {
777         error "No such mob $mob" 
778     }
779     if [string match "-device" $args] {
780         set result ""
781         set prev ""
782         foreach mob [$node path] {
783             set n [$mob_graph_ get_node $mob]
784             if [$n is_instance_of MobPort] {
785                 set d [$n device]
786                 if {$d != $prev} {
787                     lappend result [$d name]
788                     set prev $d
789                 }
790             } elseif [$n is_instance_of MobHub] {
791                 # Totally ignore 
792             } else {
793                 lappend result $mob
794                 set prev ""
795             }
796         }
797         return $result
798     } else {
799         return [$node path]
800     }
801 }
802 
803 
804 #--------------------------------------------------------------------------
805 # Method:
806 #   IndivaManager gui
807 # Description:
808 #   This is an RPC call from clients to the server.  It request the
809 #   manager to return a string representing the ui for controlling a mob,
810 #   or a segment in a flow.  GUI can be constructed in 2 ways. First,
811 #   the manager can query the capability of the mob and contruct the
812 #   ui based on the capability.   Second, the manager can query the
813 #   control object of the mob for UI, if no capability is defined.
814 #   Empty string is returned if the manager encounter an error while
815 #   talking to the control object.
816 # Arguments:
817 #   mob1 -- the media object, which we wish to control.
818 #   mob2 -- If $mob2 == $mob1 then we are controlling a device.  If 
819 #           $mob2 != $mob1, then mob1 and mob2 specifies a segment in
820 #           a flow we want to control.
821 #   parent -- the parent widget of the ui.
822 #--------------------------------------------------------------------------
823 IndivaManager public gui { from to parent args } {
824     $self instvar mob_graph_
825 
826     # Things gets a little complicated here.  1. We can either control
827     # a device, or a segment in a flow.  get_remote_service allows
828     # us to control a device. But we do not want to control a device
829     # if there is no action defined in the segment from $from to $to.
830     # (e.g. foo.rs/v.out -> vcr/v.in) So, we need to filter out these 
831     # cases here.  There got to be a more elegant way of doing things 
832     # here.  2.  The $from and $to node may not be connected directly 
833     # in $mob_graph_, there may be a hub in between.
834 
835     if {[catch {$mob_graph_ get_edge $from $to} edge]} {
836         set o [lindex [$mob_graph_ get_out_neighbors $from] 0]
837         if {[MobHub is_hub $o]} {
838             set edge [$mob_graph_ get_edge $from $o]
839         } else {
840             return ""
841         }
842     }
843 
844     set ctrl [$self get_remote_service $from $to]
845     if {$ctrl == ""} {
846         MashLog info "gui: no control for [short_name $from]. no gui."
847         return ""
848     }
849 
850     if {[catch {$ctrl get_capabilities} caps]} {
851         MashLog warn "gui: unable to get capabilities for [short_name $from]: $caps"
852         global errorInfo
853         MashLog warn "$errorInfo"
854         return ""
855     }
856 
857     # No capability defined.  Query service for UI.
858     if {$caps == ""} {
859         if {[catch {$ctrl gui $parent} result]} {
860             MashLog warn "gui: unable to get gui for [short_name $from]: $result"
861             global errorInfo
862             MashLog warn "$errorInfo"
863             return ""
864         } else {
865             return $result
866         }
867     } 
868 
869     # $mob has some capabilities.  Construct UI based on those.
870     set result {}
871     set fnode [$mob_graph_ get_node $from]
872     set tnode [$mob_graph_ get_node $to]
873     foreach {cap actions} $caps {
874         # Check if $cap is a valid class.
875         if ![catch {$cap info class}] {
876             # Valid class -> continue.
877             append result [$cap gui $fnode $tnode $parent $actions]
878         } else {
879             # Try to dynamically load the capability class.  It should have
880             # been loaded before.
881             eval import $cap
882             if {[catch {$cap info class}]} {
883                 # still cannot find the class.
884             } else {
885                 # import success.  Get to gui.
886                 append result [$cap gui $fnode $tnode $parent $actions]
887             }
888         }
889     }
890     return $result
891 }
892 
893 
894 #--------------------------------------------------------------------------
895 # Method:
896 #   IndivaManager control
897 # Description:
898 #   This is an RPC call from clients to the server.  It request the manager 
899 #   to send a control command to a service.
900 # Arguments:
901 #   from,to -- the media objects, which we wish to control.  
902 #   args -- the control message to send.
903 #--------------------------------------------------------------------------
904 IndivaManager public control { from to args } {
905     set args [join $args]
906 
907     set service [$self get_remote_service $from $to]
908     if {[catch {$service do $args} result]} {
909         MashLog warn "control: unable to control $from: $result"
910         global errorInfo
911         MashLog warn "$errorInfo"
912     }
913 }
914 
915 
916 #--------------------------------------------------------------------------
917 # Method:
918 #   IndivaManager event
919 # Description:
920 #   This is an RPC call from clients to the server.  It request the manager 
921 #   to send a control command to a service.
922 # Arguments:
923 #   mob -- the media object, which we wish to control.
924 #   args -- the control message to send.
925 #--------------------------------------------------------------------------
926 IndivaManager public event { event mob args } {
927     MashLog info "event: $event $mob $args"
928     if {$mob == ""} {
929         return
930     }
931     set args [join $args]
932     $self instvar mob_graph_
933     set local_name [$self map_to_local_name $mob]
934     set real_name  [$self resolve_link $local_name]
935     set mob_node [$mob_graph_ get_node $real_name]
936     set mob_list [$self path $mob]
937     set from [lindex $mob_list 0]
938     set result ""
939 
940     # Special case for dropping into Kaleido