Class: SMS::Router

Inherits:
Object
  • Object
show all
Defined in:
lib/rubysms/router.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeRouter

Returns a new instance of Router.



9
10
11
12
13
# File 'lib/rubysms/router.rb', line 9

def initialize
	@log = Logger.new(STDOUT)
	@backends = []
	@apps = []
end

Instance Attribute Details

#appsObject (readonly)

Returns the value of attribute apps.



6
7
8
# File 'lib/rubysms/router.rb', line 6

def apps
  @apps
end

#backendsObject (readonly)

Returns the value of attribute backends.



6
7
8
# File 'lib/rubysms/router.rb', line 6

def backends
  @backends
end

Instance Method Details

#add(something) ⇒ Object

Accepts an SMS::Backend::Base or SMS::App instance, which is stored until serve_forever is called. DEPRECATED because it’s confusing and magical.



98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
# File 'lib/rubysms/router.rb', line 98

def add(something)
	log "Router#add is deprecated; use " +\
	    "#add_backend and #add_app", :warn
	
	if something.is_a? SMS::Backend::Base
		@backends.push(something)
	
	elsif something.is_a? SMS::App
		@apps.push(something)
	
	else
		raise RuntimeError,
			"Router#add doesn't know what " +\
			"to do with a #{something.klass}"
	end
	
	# store a reference back to this router in
	# the app or backend, so it can talk back
	something.router = self
end

#add_app(app) ⇒ Object

Adds an SMS application (which is usually an instance of a subclass of SMS::App, but anything’s fine, so long as it quacks the right way) to this router, which will be started once serve_forever is called.



122
123
124
125
# File 'lib/rubysms/router.rb', line 122

def add_app(app)
	@apps.push(app)
	app.router = self
end

#add_backend(backend, *args) ⇒ Object

Adds an SMS backend (which MUST be is_a?(SMS::Backend::Base), for now), or a symbol representing a loadable SMS backend, which is passed on to SMS::Backend.create (along with *args) to be required and initialized. This only really works with built-in backends, for now, but is useful for initializing those:

# start serving with a single
# http backend on port 9000
router = SMS::Router.new
router.add_backend(:HTTP, 9000)
router.serve_forever

# start serving on two gsm
# modems with pin numbers
router = SMS::Router.new
router.add_backend(:GSM, "/dev/ttyS0", 1234)
router.add_backend(:GSM, "/dev/ttyS1", 5678)
router.serve_forever


145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
# File 'lib/rubysms/router.rb', line 145

def add_backend(backend, *args)
	
	# if a backend object was given, add it to this router
	# TODO: this modifies the argument just slightly. would
	# it be better to duplicate the object first?
	if backend.is_a?(SMS::Backend::Base)
		@backends.push(backend)
		backend.router = self
	
	# if it's a named backend, spawn it (along
	# with the optional arguments) and recurse
	elsif backend.is_a?(Symbol)
		add_backend SMS::Backend.create(backend, *args)
	end
end

#incoming(msg) ⇒ Object

Relays a given incoming message from a specific backend to all applications.



163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
# File 'lib/rubysms/router.rb', line 163

def incoming(msg)
	log_with_time "[#{msg.backend.label}] #{msg.sender.key}: #{msg.text} (#{msg.text.length})", :in
	
	# notify each application of the message.
	# they may or may not respond to it
	@apps.each do |app|
		begin
			app.incoming msg
		
		# something went boom in the app
		# log it, and continue with the next
		rescue StandardError => err
			log_exception(err)
			
		#	if msg.responses.empty?
		#		msg.respond("Sorry, there was an error while processing your message.")
		#	end
		end
	end
end

#log(*args) ⇒ Object

proxy methods to pass events to the logger with the pretty



19
20
21
# File 'lib/rubysms/router.rb', line 19

def log(*args)
	@log.event(*args)
end

#log_exception(error, prefix_message = nil) ⇒ Object



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
# File 'lib/rubysms/router.rb', line 27

def log_exception(error, prefix_message=nil)
	msgs = [error.class, error.message]
	
	# if a prefix was provided (to give a litle
	# more info on what went wrong), prepend it
	# to the output with a blank line
	unless prefix_message.nil?
		msg.shift prefix_message, ""
	end
	
	# add each line until the current frame is within
	# rubysms (the remainder will just be from gems)
	catch(:done) do
		error.backtrace.each do |line|
			if line =~ /^#{SMS::Root}/
				throw :done
			end
			
			# still within the application,
			# so add the frame to the log
			msgs.push("  " + line)
		end
	end
	
	@log.event msgs, :warn
end

#log_with_time(*args) ⇒ Object



23
24
25
# File 'lib/rubysms/router.rb', line 23

def log_with_time(*args)
	@log.event_with_time(*args)
end

#outgoing(msg) ⇒ Object

Notifies each application of an outgoing message, and logs it. Should be called by all backends prior to sending.



186
187
188
189
190
191
192
193
194
195
196
# File 'lib/rubysms/router.rb', line 186

def outgoing(msg)
	log_with_time "[#{msg.backend.label}] #{msg.recipient.key}: #{msg.text} (#{msg.text.length})", :out
	log("Outgoing message exceeds 140 characters", :warn) if msg.text.length > 140
	cancelled = false
	
	# notify each app of the outgoing sms
	# note that the sending can still fail
	@apps.each do |app|
		app.outgoing msg
	end
end

#serve_foreverObject

Starts listening for incoming messages on all backends, and never returns.



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
# File 'lib/rubysms/router.rb', line 57

def serve_forever
	
	# (attempt to) start up each
	# backend in a separate thread
	@backends.each do |b|
		Thread.new do
			b.start
		end
	end
	
	# applications don't need their own
	# thread (they're notified in serial),
	# but do have a #start method
	@apps.each { |a| a.start }

	# catch interrupts and display a nice message (rather than
	# a backtrace). to avoid seeing control characters (^C) in
	# the output, disable the "echoctl" option in your terminal
	# (i added "stty -echoctl" to my .bashrc)
	trap("INT") do
		log "Shutting down", :init
	
		# fire the "stop" method of
		# each application and backend
		# before terminating the process
		(@backends + @apps).each do |inst|
			inst.stop
		end
	
		exit
	end

	# block until ctrl+c
	while true do
		sleep 5
	end
end