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