File: proxy_object.rb

package info (click to toggle)
ruby-dbus 0.16.0-1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm, bullseye, forky, sid, trixie
  • size: 520 kB
  • sloc: ruby: 3,786; sh: 53; makefile: 8
file content (154 lines) | stat: -rw-r--r-- 5,344 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
# This file is part of the ruby-dbus project
# Copyright (C) 2007 Arnaud Cornet and Paul van Tilburg
# Copyright (C) 2009-2014 Martin Vidner
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License, version 2.1 as published by the Free Software Foundation.
# See the file "COPYING" for the exact licensing terms.

module DBus
  # D-Bus proxy object class
  #
  # Class representing a remote object in an external application.
  # Typically, calling a method on an instance of a ProxyObject sends a message
  # over the bus so that the method is executed remotely on the correctponding
  # object.
  class ProxyObject
    # The names of direct subnodes of the object in the tree.
    attr_accessor :subnodes
    # Flag determining whether the object has been introspected.
    attr_accessor :introspected
    # The (remote) destination of the object.
    attr_reader :destination
    # The path to the object.
    attr_reader :path
    # The bus the object is reachable via.
    attr_reader :bus
    # @return [String] The name of the default interface of the object.
    attr_accessor :default_iface
    # @api private
    # @return [ApiOptions]
    attr_reader :api

    # Creates a new proxy object living on the given _bus_ at destination _dest_
    # on the given _path_.
    def initialize(bus, dest, path, api: ApiOptions::CURRENT)
      @bus = bus
      @destination = dest
      @path = ObjectPath.new(path)
      @introspected = false
      @interfaces = {}
      @subnodes = []
      @api = api
    end

    # Returns the interfaces of the object.
    def interfaces
      introspect unless introspected
      @interfaces.keys
    end

    # Retrieves an interface of the proxy object
    # @param [String] intfname
    # @return [ProxyObjectInterface]
    def [](intfname)
      introspect unless introspected
      ifc = @interfaces[intfname]
      raise DBus::Error, "no such interface `#{intfname}' on object `#{@path}'" unless ifc
      ifc
    end

    # Maps the given interface name _intfname_ to the given interface _intf.
    # @param [String] intfname
    # @param [ProxyObjectInterface] intf
    # @return [ProxyObjectInterface]
    # @api private
    def []=(intfname, intf)
      @interfaces[intfname] = intf
    end

    # Introspects the remote object. Allows you to find and select
    # interfaces on the object.
    def introspect
      # Synchronous call here.
      xml = @bus.introspect_data(@destination, @path)
      ProxyObjectFactory.introspect_into(self, xml)
      define_shortcut_methods
      xml
    end

    # For each non duplicated method name in any interface present on the
    # caller, defines a shortcut method dynamically.
    # This function is automatically called when a {ProxyObject} is
    # introspected.
    def define_shortcut_methods
      # builds a list of duplicated methods
      dup_meths = []
      univocal_meths = {}
      @interfaces.each_value do |intf|
        intf.methods.each_value do |meth|
          name = meth.name.to_sym
          # don't overwrite instance methods!
          next if dup_meths.include?(name)
          next if self.class.instance_methods.include?(name)
          if univocal_meths.include? name
            univocal_meths.delete name
            dup_meths << name
          else
            univocal_meths[name] = intf
          end
        end
      end
      univocal_meths.each do |name, intf|
        # creates a shortcut function that forwards each call to the method on
        # the appropriate intf
        singleton_class.class_eval do
          redefine_method name do |*args, &reply_handler|
            intf.method(name).call(*args, &reply_handler)
          end
        end
      end
    end

    # Returns whether the object has an interface with the given _name_.
    def has_iface?(name)
      introspect unless introspected
      @interfaces.key?(name)
    end

    # Registers a handler, the code block, for a signal with the given _name_.
    # It uses _default_iface_ which must have been set.
    # @return [void]
    def on_signal(name, &block)
      # TODO: improve
      raise NoMethodError unless @default_iface && has_iface?(@default_iface)
      @interfaces[@default_iface].on_signal(name, &block)
    end

    ####################################################
    private

    # Handles all unkown methods, mostly to route method calls to the
    # default interface.
    def method_missing(name, *args, &reply_handler)
      unless @default_iface && has_iface?(@default_iface)
        # TODO: distinguish:
        # - di not specified
        # TODO
        # - di is specified but not found in introspection data
        raise NoMethodError, "undefined method `#{name}' for DBus interface `#{@default_iface}' on object `#{@path}'"
      end

      begin
        @interfaces[@default_iface].method(name).call(*args, &reply_handler)
      rescue NameError => e
        # interesting, foo.method("unknown")
        # raises NameError, not NoMethodError
        raise unless e.to_s =~ /undefined method `#{name}'/
        # BTW e.exception("...") would preserve the class.
        raise NoMethodError, "undefined method `#{name}' for DBus interface `#{@default_iface}' on object `#{@path}'"
      end
    end
  end # class ProxyObject
end