Class: AdmConnection
- Inherits:
-
Object
- Object
- AdmConnection
- Includes:
- Singleton
- Defined in:
- lib/smsruby/adm_connection.rb
Overview
The AdmConnection class represent the SMS connection management layer for handling and controling connections (in case a pool of connections exist). It will also control the flow and the balance of the messages to be deliver through all active connections
Constant Summary collapse
- @@connections =
Represent a hash containing all connections availables to send or receive
{}
- @@consu =
Reference all the consumer threads
[]
Instance Attribute Summary collapse
-
#avlconn ⇒ Object
readonly
Specify if there is iniialized connections or not.
-
#config ⇒ Object
readonly
Reference the config file for smsRuby.
-
#consumed ⇒ Object
readonly
Represent the number of items consumed of the total produced.
-
#log ⇒ Object
readonly
Reference the log system to register the events.
-
#options ⇒ Object
readonly
Represent a hash that groups all SMS delivery options.
-
#ports ⇒ Object
readonly
Represent all the possible ports.
-
#produced ⇒ Object
readonly
Represent the number of items produced into de queue.
-
#sync ⇒ Object
readonly
Reference a syncronization object to control access to critical resources.
Instance Method Summary collapse
-
#check_phone(phone) ⇒ Object
Check the validity of the phone number format.
-
#consumer(n, max) ⇒ Object
Extract a destination number from the shared buffer and passed along with SMS message option values to the excetute connection function.
-
#get_connections ⇒ Object
obtains all available connections an execute a code block for each connection found if a code block is received.
-
#initialize ⇒ AdmConnection
constructor
Initialize the admin variables, the system log and the synchronization object.
-
#open_conn(name, port) ⇒ Object
Initialize a new connection with the specifyed name and port and add it to the connections hash, wich holds all available connections.
-
#open_ports(ports = nil) ⇒ Object
Set specific ports to checked.
-
#open_profiles ⇒ Object
Used to open connections and load configuration file.
-
#producer(dest) ⇒ Object
Put all destination numbers (produce) into a shared buffer.
-
#receive(hash) ⇒ Object
The internal receive function for the Connection Administrator.
-
#reload_file ⇒ Object
reload the configuration file to update changes.
-
#retryable(options = {}, &block) ⇒ Object
Handles retry for a particular code block.
-
#save_file(array) ⇒ Object
Save the configuration file used for gnokii to load phone profiles and open connections.
-
#send(config) ⇒ Object
The internal send function for the Connection Administrator.
-
#send_emergency(n, max) ⇒ Object
Handles all unsend messages from the consumers due to diferent exceptions (no signal in phone, error in sim card..).
-
#to_hash(num) ⇒ Object
Combine all option values into a hash to relate them.
-
#update_connections ⇒ Object
Check if a new connection had been attach.
-
#verify(total, seconds) ⇒ Object
Excecute every “seconds” for a period of “total” seconds a given code block.
-
#verify_connection(seconds) ⇒ Object
Check if a new connection had been attach while sending a message.
Constructor Details
#initialize ⇒ AdmConnection
Initialize the admin variables, the system log and the synchronization object
42 43 44 45 46 47 48 49 50 |
# File 'lib/smsruby/adm_connection.rb', line 42 def initialize @sync=Synchronize.new ={} @log=Logger.new('sms.log') @ports=[] @produced=0 @consumed=0 @avlconn=false end |
Instance Attribute Details
#avlconn ⇒ Object (readonly)
Specify if there is iniialized connections or not
36 37 38 |
# File 'lib/smsruby/adm_connection.rb', line 36 def avlconn @avlconn end |
#config ⇒ Object (readonly)
Reference the config file for smsRuby
32 33 34 |
# File 'lib/smsruby/adm_connection.rb', line 32 def config @config end |
#consumed ⇒ Object (readonly)
Represent the number of items consumed of the total produced
26 27 28 |
# File 'lib/smsruby/adm_connection.rb', line 26 def consumed @consumed end |
#log ⇒ Object (readonly)
Reference the log system to register the events
30 31 32 |
# File 'lib/smsruby/adm_connection.rb', line 30 def log @log end |
#options ⇒ Object (readonly)
Represent a hash that groups all SMS delivery options
28 29 30 |
# File 'lib/smsruby/adm_connection.rb', line 28 def end |
#ports ⇒ Object (readonly)
Represent all the possible ports
34 35 36 |
# File 'lib/smsruby/adm_connection.rb', line 34 def ports @ports end |
#produced ⇒ Object (readonly)
Represent the number of items produced into de queue
24 25 26 |
# File 'lib/smsruby/adm_connection.rb', line 24 def produced @produced end |
#sync ⇒ Object (readonly)
Reference a syncronization object to control access to critical resources
22 23 24 |
# File 'lib/smsruby/adm_connection.rb', line 22 def sync @sync end |
Instance Method Details
#check_phone(phone) ⇒ Object
Check the validity of the phone number format
471 472 473 474 475 |
# File 'lib/smsruby/adm_connection.rb', line 471 def check_phone(phone) phone_re = /^(\+\d{1,3}\d{3}\d{7})|(0\d{3})\d{7}$/ m = phone_re.match(phone.to_s) m ? true : false end |
#consumer(n, max) ⇒ Object
Extract a destination number from the shared buffer and passed along with SMS message option values to the excetute connection function. A synchronization with the producer and all other consumers is required to avoid data loss and incoherence
284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 |
# File 'lib/smsruby/adm_connection.rb', line 284 def consumer(n,max) Thread.current[:wfs]=0 Thread.current[:type]='s' Thread.current[:connid]=n loop do @sync.mutexp.synchronize{ (@@connections[n].status="available"; Thread.exit) if (@produced >= max && @sync.queue.empty?) } begin @sync.mutex.synchronize{ while (@sync.count == 0) Thread.current[:wfs]=1 @sync.empty.wait(@sync.mutex) Thread.current[:wfs]=0 end Thread.current[:v] = @sync.queue.pop #puts ":: Consumer: in connection #{n} #{Thread.current[:v]} consumed \n" @sync.full.signal if (@sync.count == (@sync.max - 1)) } retryable(:tries => 2, :on => ErrorHandler::Error) do @@connections[n].execute(to_hash(Thread.current[:v].to_s)) end @consumed+=1 @log.info "Message: #{@options[:msj][0,15]}... to #{Thread.current[:v].to_s} sent succsefull from connection with imei #{@@connections[n].phone_imei}." unless @log.nil? rescue ErrorHandler::Error => ex @log.error "Connection in port #{@@connections[n].port} fail sending message to #{Thread.current[:v]}, Message sent to emergency queue. Exception:: #{ex.message}" unless @log.nil? @sync.eq.push(Thread.current[:v]) rescue Exception => ex @log.error "Connection in port #{@@connections[n].port} fail sending message to #{Thread.current[:v]}. Exception:: #{ex.message}" unless @log.nil? end end end |
#get_connections ⇒ Object
obtains all available connections an execute a code block for each connection found if a code block is received
56 57 58 59 |
# File 'lib/smsruby/adm_connection.rb', line 56 def get_connections @@connections.each{ |i,c| yield c if c.status!="disable"} if block_given? return @@connections end |
#open_conn(name, port) ⇒ Object
Initialize a new connection with the specifyed name and port and add it to the connections hash, wich holds all available connections. If an exception is thrown an error will be register in the system log
172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 |
# File 'lib/smsruby/adm_connection.rb', line 172 def open_conn(name,port) begin open=true n = @@connections.size con = Connection.new(name,port) (@config['send']['imeis']).each { |item|con.typec = 's' if con.phone_imei.eql?(item.to_s)} (@config['receive']['imeis']).each {|item| (con.typec == 's' ? con.typec ='sr' : con.typec = 'r') if con.phone_imei.eql?(item.to_s) } con.typec = 's' if (con.typec!= 'r' and con.typec!='sr') puts ":: satisfactorily open a connection in port #{port} imei is #{con.phone_imei} and connection type is: #{con.typec} ::\n" @@connections.merge!({n => con}) rescue ErrorHandler::Error => e #@log.info "Openning connection in #{port}.. #{e.message} Not Device Found " unless @log.nil? open=false rescue Exception => e @log.info "Openning connection in port #{port}.. #{e.message}" unless @log.nil? open=false end open end |
#open_ports(ports = nil) ⇒ Object
Set specific ports to checked. Try to open all specified connections and create. Raise an exception if not functional connection is found. A call to open_profile is made to open connections in the given ports
66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 |
# File 'lib/smsruby/adm_connection.rb', line 66 def open_ports(ports=nil) open=false #if RUBY_PLATFORM =~ /(win|w)32$/ #9.times { |i| @ports.push("COM#{i}")} if RUBY_PLATFORM =~ /linux/ and ports.nil? 9.times { |i| @ports.push("/dev/ttyUSB#{i}") @ports.push("/dev/ttyACM#{i}") } else if ports.nil? @log.error "No ports were specified" raise "No ports were specified" else ports.each{|p| @ports.push(p)} end end open = open_profiles @log.error "No active connections found or available connections fail" unless open raise 'No active connections found or available connections fail. See log for details' unless open end |
#open_profiles ⇒ Object
Used to open connections and load configuration file. Return true if at least one connection could be open satisfactorily, false otherwise
92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 |
# File 'lib/smsruby/adm_connection.rb', line 92 def open_profiles begin open=false save_file(@ports) path = 'config_sms.yml' parse=YAML::parse(File.open(path)) @config=parse.transform @ports.size.times do |i| open=open_conn("telf"+i.to_s,@ports[i]) || open end @avlconn=true if open open rescue Exception => e @log.error "Error openning connections. Detail #{e.message}" end end |
#producer(dest) ⇒ Object
Put all destination numbers (produce) into a shared buffer. A synchronization with all active consumers is required to avoid data loss and incoherence. The buffer has a limited size, so is up to the producer to handle this matter
262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 |
# File 'lib/smsruby/adm_connection.rb', line 262 def producer(dest) dest.each do |i| begin @sync.mutex.synchronize{ @sync.full.wait(@sync.mutex) if (@sync.count == @sync.max) @sync.queue.push i.to_s #puts "Producer: #{i} produced"+"\n" @sync.mutexp.synchronize{ @produced += 1 } @sync.empty.signal if @sync.count == 1 } end end end |
#receive(hash) ⇒ Object
The internal receive function for the Connection Administrator.
404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 |
# File 'lib/smsruby/adm_connection.rb', line 404 def receive(hash) begin conn=nil @@connections.each{|i,c| if c.phone_imei==hash[:imei] @log.info "Start receiving messages from connection with imei: #{hash[:imei]}." unless log.nil? conn=c break end } unless !conn if hash[:receivetype]==0 verify(hash[:time],10){ list = conn.execute(hash) unless !list list.each do |msj| @log.info "Message received in connection with imei #{conn.phone_imei} from #{msj.source_number}. #{msj.text}." yield msj,conn.phone_imei if block_given? end end } else list = conn.execute(hash) unless !list list.each do |msj| @log.info "Message received in connection with imei #{conn.phone_imei} from #{msj.source_number}. #{msj.text}." yield msj,conn.phone_imei if block_given? end end end conn.status='available' end rescue ErrorHandler::Error => e error = "Fail to receive more messages from connecion with imei #{hash[:imei]}. Detail: #{e.message}" @log.error error unless @log.nil? conn.status='available' raise error rescue Exception => e error = "Exception receiving messages from connecion with imei #{hash[:imei]}. Detail: #{e.message}" @log.error error unless @log.nil? conn.status='available' raise error end end |
#reload_file ⇒ Object
reload the configuration file to update changes
127 128 129 130 |
# File 'lib/smsruby/adm_connection.rb', line 127 def reload_file parse=YAML::parse(File.open('config_sms.yml')) @config=parse.transform end |
#retryable(options = {}, &block) ⇒ Object
Handles retry for a particular code block. The default numbers of retrys is set to 1. The retry will be executed on any exception unless a type of error is specified
454 455 456 457 458 459 460 461 462 463 464 465 466 |
# File 'lib/smsruby/adm_connection.rb', line 454 def retryable( = {}, &block) opts = { :tries => 1, :on => Exception }.merge() retry_exception, retries = opts[:on], opts[:tries] begin return yield rescue retry_exception retry if (retries -= 1) > 0 end yield end |
#save_file(array) ⇒ Object
Save the configuration file used for gnokii to load phone profiles and open connections. The required information is specifyed in array
136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 |
# File 'lib/smsruby/adm_connection.rb', line 136 def save_file (array) begin i = 0 if RUBY_PLATFORM =~ /(win|w)32$/ path = ENV['userprofile']+'/_gnokiirc' elsif RUBY_PLATFORM =~ /linux/ path = ENV['HOME']+'/.gnokiirc' end File.open(path, 'w') do |f2| array.each do |pos| f2.puts "[phone_telf" + i.to_s + "]" f2.puts "port = " + pos f2.puts "model = AT" f2.puts "connection = serial" i = i+1 end f2.puts "[global]" f2.puts "port = COM3" f2.puts "model = AT" f2.puts "connection = serial" f2.puts '[logging]' f2.puts 'debug = off' f2.puts 'rlpdebug = off' end rescue SystemCallError @log.error "Problem writing the configuration file" unless @log.nil? raise "Problem writing the configuration file" end end |
#send(config) ⇒ Object
The internal send function for the Connection Administrator. The config value specify the option values for the SMS messages to be send. It starts producer and consumers to distribute the messages to the diferent available connections. A recovery send is started if at least one message fail to deliver in the first attempt.
200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 |
# File 'lib/smsruby/adm_connection.rb', line 200 def send(config) = config config[:dst].delete_if {|x| (!check_phone(x) and (@log.error "Incorrect phone number format for #{x}"if !check_phone(x)))} if !config[:dst].empty? if !config[:dst].empty? bool= !(@@connections.inject(true){|res,act| (act[1].status == "disable" or (act[1].typec=='r' or (act[1].typec=='sr' and act[1].status=="receiving"))) and res }) if !@@connections.empty? if !@@connections.empty? and bool @log.info "Starting send.. #{config[:dst].size} messages to be sent. " unless @log.nil? prod = Thread.new{producer(config[:dst])} conn=Thread.new{verify_connection(5)} pos=0 @@connections.each do |i,c| unless c.typec=='r' or (c.typec=='sr' and c.status=="receiving") or c.status =="disable" @@consu[pos] = Thread.new{consumer(i,config[:dst].size)} pos+=1 end end prod.join @@consu.each { |c| (c.stop? and c[:wfs]==1) ? (@@connections[c[:connid]].status="available"; c.exit) : c.join } @@consu.clear unless @sync.eq.empty? pos=0 @@connections.each do |i,c| unless c.typec=='r' or (c.typec=='sr' and c.status=="receiving" ) or c.status =="disable" @@consu[pos]=Thread.new{send_emergency(i,config[:dst].size)} pos+=1 end end @@consu.each { |c| (c.stop? and c[:wfs]==1) ? (@@connections[c[:connid]].status="available"; c.exit) : c.join } check=0 while(!@sync.eq.empty?) check=1 @log.error "Message: #{config[:msj][0,15]}... to #{@sync.eq.pop} couldn't be sent." unless @log.nil? end conn.exit error = "Message #{config[:msj][0,15]}... couldn't be sent to some destinations. See log for details." if !check yield error if block_given? raise error if check end conn.exit else warn = "Message: #{config[:msj][0,15]}... couldn't be sent. There are no active or available to send connections" @log.warn warn unless @log.nil? yield warn if block_given? raise warn end else warn = "Message: #{config[:msj][0,15]}... couldn't be sent. Bad format or no destination number were specified" @log.warn warn unless @log.nil? yield warn if block_given? raise warn end end |
#send_emergency(n, max) ⇒ Object
Handles all unsend messages from the consumers due to diferent exceptions (no signal in phone, error in sim card..). Try to send the messages recovering it from an emergency queue and discarting it only if none of the active connections is able to send the message.
323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 |
# File 'lib/smsruby/adm_connection.rb', line 323 def send_emergency(n,max) Thread.current[:wfs]=0 Thread.current[:type]='s' Thread.current[:connid]=n loop do begin @sync.mutexe.synchronize{ if (@sync.eq.size == 0 and @consumed < max) Thread.current[:wfs] = 1 @sync.emptye.wait(@sync.mutexe) Thread.current[:wfs] = 0 elsif (@consumed == max) @@connections[n].status="available"; Thread.exit end unless @sync.eq.empty? Thread.current[:v]=@sync.eq.pop retryable(:tries => 2, :on => ErrorHandler::Error) do @@connections[n].execute(to_hash(Thread.current[:v].to_s)) end @consumed+=1 @log.info "EMessage: #{@options[:msj][0,15]}... to #{Thread.current[:v].to_s} sent succsefull from connection with imei #{@@connections[n].phone_imei}." unless @log.nil? p ':: Emergency message consumed '+(max-@consumed).to_s+' left' (Thread.list.each{|t| @sync.emptye.signal if (t!=Thread.current and t!=Thread.main and t[:wfs]==1)}) if @consumed == max end } rescue Exception => e @sync.mutexe.synchronize{ @log.error "Connection in port #{@@connections[n].port} fail sending message to #{Thread.current[:v]} at emergency function. Exception:: #{e.message}" unless @log.nil? @sync.eq << Thread.current[:v] @sync.emptye.signal if @sync.eq.size==1 } @@connections[n].status="available"; Thread.exit end end end |
#to_hash(num) ⇒ Object
Combine all option values into a hash to relate them
480 481 482 483 484 485 486 487 |
# File 'lib/smsruby/adm_connection.rb', line 480 def to_hash(num) { :type => 'send', :dst => num, :msj => self.[:msj], :smsc =>self.[:smsc], :report => self.[:report], :validity => self.[:validity]} end |
#update_connections ⇒ Object
Check if a new connection had been attach.
112 113 114 115 116 117 118 119 120 121 122 |
# File 'lib/smsruby/adm_connection.rb', line 112 def update_connections begin @@connections.each{ |i,c| t=c.test; c.status = "disable" if (t!=0 and t!=22 and t!=23)} @ports.select{ |i| (!@@connections.inject(false){|res,act| (act[1].port == i and act[1].status!="disable") || res }) }.each{|i| open_conn("telf"+@ports.index(i).to_s,i) } rescue Exception => e puts "An error has occurred updating connections. Exception:: #{e.message}\n" @log.error "An error has occurred updating connections. Exception:: #{e.message}" unless @log.nil? end end |
#verify(total, seconds) ⇒ Object
Excecute every “seconds” for a period of “total” seconds a given code block. If “total” is 0 the function will loop forever
388 389 390 391 392 393 394 395 396 397 398 399 |
# File 'lib/smsruby/adm_connection.rb', line 388 def verify(total,seconds) start_total=Time.now loop do start_time = Time.now puts "Task started. #{start_time}" yield time_spent=Time.now - start_time puts "Task donde. #{Time.now}"+ "and spend #{time_spent}" break if ((Time.now - start_total) >= total and total != 0) sleep(seconds - time_spent) if time_spent < seconds end end |
#verify_connection(seconds) ⇒ Object
Check if a new connection had been attach while sending a message. This allows to start new consumers dinamically and increase the performance
365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 |
# File 'lib/smsruby/adm_connection.rb', line 365 def verify_connection(seconds) begin n=0 verify(0,seconds){ @ports.select{ |i| (!@@connections.inject(false){|res,act| (act[1].port == i) || res }) }.each{|i| n=@@connections.size if open_conn("telf"+@ports.index(i).to_s,i) unless @@connections[n].typec=='r' or (@@connections[n].typec=='sr' and @@connections[n].status="receiving") @@consu[n]=Thread.new{consumer(n,[:dst].size)} end end } } rescue Exception => e puts "An error has occurred during execution of verify connection function. Exception:: #{e.message}" @log.error "An error has occurred during execution of verify connection function. Exception:: #{e.message}" unless @log.nil? end end |