| 1 | module Irc |
|---|
| 2 | |
|---|
| 3 | require 'socket' |
|---|
| 4 | require 'thread' |
|---|
| 5 | |
|---|
| 6 | # wrapped TCPSocket for communication with the server. |
|---|
| 7 | # emulates a subset of TCPSocket functionality |
|---|
| 8 | class IrcSocket |
|---|
| 9 | # total number of lines sent to the irc server |
|---|
| 10 | attr_reader :lines_sent |
|---|
| 11 | # total number of lines received from the irc server |
|---|
| 12 | attr_reader :lines_received |
|---|
| 13 | # server:: server to connect to |
|---|
| 14 | # port:: IRCd port |
|---|
| 15 | # host:: optional local host to bind to (ruby 1.7+ required) |
|---|
| 16 | # create a new IrcSocket |
|---|
| 17 | def initialize(server, port, host, sendfreq=2, maxburst=4) |
|---|
| 18 | @server = server.dup |
|---|
| 19 | @port = port.to_i |
|---|
| 20 | @host = host |
|---|
| 21 | @lines_sent = 0 |
|---|
| 22 | @lines_received = 0 |
|---|
| 23 | if sendfreq |
|---|
| 24 | @sendfreq = sendfreq.to_f |
|---|
| 25 | else |
|---|
| 26 | @sendfreq = 2 |
|---|
| 27 | end |
|---|
| 28 | @last_send = Time.new - @sendfreq |
|---|
| 29 | @burst = 0 |
|---|
| 30 | if maxburst |
|---|
| 31 | @maxburst = maxburst.to_i |
|---|
| 32 | else |
|---|
| 33 | @maxburst = 4 |
|---|
| 34 | end |
|---|
| 35 | end |
|---|
| 36 | |
|---|
| 37 | # open a TCP connection to the server |
|---|
| 38 | def connect |
|---|
| 39 | if(@host) |
|---|
| 40 | begin |
|---|
| 41 | @sock=TCPSocket.new(@server, @port, @host) |
|---|
| 42 | rescue ArgumentError => e |
|---|
| 43 | $stderr.puts "Your version of ruby does not support binding to a " |
|---|
| 44 | $stderr.puts "specific local address, please upgrade if you wish " |
|---|
| 45 | $stderr.puts "to use HOST = foo" |
|---|
| 46 | $stderr.puts "(this option has been disabled in order to continue)" |
|---|
| 47 | @sock=TCPSocket.new(@server, @port) |
|---|
| 48 | end |
|---|
| 49 | else |
|---|
| 50 | @sock=TCPSocket.new(@server, @port) |
|---|
| 51 | end |
|---|
| 52 | @qthread = false |
|---|
| 53 | @qmutex = Mutex.new |
|---|
| 54 | @sendq = Array.new |
|---|
| 55 | if (@sendfreq > 0) |
|---|
| 56 | @qthread = Thread.new { spooler } |
|---|
| 57 | end |
|---|
| 58 | end |
|---|
| 59 | |
|---|
| 60 | def set_sendq(newfreq) |
|---|
| 61 | debug "changing sendq frequency to #{newfreq}" |
|---|
| 62 | @qmutex.synchronize do |
|---|
| 63 | @sendfreq = newfreq |
|---|
| 64 | if newfreq == 0 && @qthread |
|---|
| 65 | clearq |
|---|
| 66 | Thread.kill(@qthread) |
|---|
| 67 | @qthread = false |
|---|
| 68 | elsif(newfreq != 0 && !@qthread) |
|---|
| 69 | @qthread = Thread.new { spooler } |
|---|
| 70 | end |
|---|
| 71 | end |
|---|
| 72 | end |
|---|
| 73 | |
|---|
| 74 | def set_maxburst(newburst) |
|---|
| 75 | @qmutex.synchronize do |
|---|
| 76 | @maxburst = newburst |
|---|
| 77 | end |
|---|
| 78 | end |
|---|
| 79 | |
|---|
| 80 | def get_maxburst |
|---|
| 81 | return @maxburst |
|---|
| 82 | end |
|---|
| 83 | |
|---|
| 84 | def get_sendq |
|---|
| 85 | return @sendfreq |
|---|
| 86 | end |
|---|
| 87 | |
|---|
| 88 | # used to send lines to the remote IRCd |
|---|
| 89 | # message: IRC message to send |
|---|
| 90 | def puts(message) |
|---|
| 91 | @qmutex.synchronize do |
|---|
| 92 | # debug "In puts - got mutex" |
|---|
| 93 | puts_critical(message) |
|---|
| 94 | end |
|---|
| 95 | end |
|---|
| 96 | |
|---|
| 97 | # get the next line from the server (blocks) |
|---|
| 98 | def gets |
|---|
| 99 | reply = @sock.gets |
|---|
| 100 | @lines_received += 1 |
|---|
| 101 | if(reply) |
|---|
| 102 | reply.strip! |
|---|
| 103 | end |
|---|
| 104 | debug "RECV: #{reply.inspect}" |
|---|
| 105 | reply |
|---|
| 106 | end |
|---|
| 107 | |
|---|
| 108 | def queue(msg) |
|---|
| 109 | if @sendfreq > 0 |
|---|
| 110 | @qmutex.synchronize do |
|---|
| 111 | # debug "QUEUEING: #{msg}" |
|---|
| 112 | @sendq.push msg |
|---|
| 113 | end |
|---|
| 114 | else |
|---|
| 115 | # just send it if queueing is disabled |
|---|
| 116 | self.puts(msg) |
|---|
| 117 | end |
|---|
| 118 | end |
|---|
| 119 | |
|---|
| 120 | def spooler |
|---|
| 121 | while true |
|---|
| 122 | spool |
|---|
| 123 | sleep 0.1 |
|---|
| 124 | end |
|---|
| 125 | end |
|---|
| 126 | |
|---|
| 127 | # pop a message off the queue, send it |
|---|
| 128 | def spool |
|---|
| 129 | unless @sendq.empty? |
|---|
| 130 | now = Time.new |
|---|
| 131 | if (now >= (@last_send + @sendfreq)) |
|---|
| 132 | # reset burst counter after @sendfreq has passed |
|---|
| 133 | @burst = 0 |
|---|
| 134 | debug "in spool, resetting @burst" |
|---|
| 135 | elsif (@burst >= @maxburst) |
|---|
| 136 | # nope. can't send anything |
|---|
| 137 | return |
|---|
| 138 | end |
|---|
| 139 | @qmutex.synchronize do |
|---|
| 140 | debug "(can send #{@maxburst - @burst} lines, there are #{@sendq.length} to send)" |
|---|
| 141 | (@maxburst - @burst).times do |
|---|
| 142 | break if @sendq.empty? |
|---|
| 143 | puts_critical(@sendq.shift) |
|---|
| 144 | end |
|---|
| 145 | end |
|---|
| 146 | end |
|---|
| 147 | end |
|---|
| 148 | |
|---|
| 149 | def clearq |
|---|
| 150 | unless @sendq.empty? |
|---|
| 151 | @qmutex.synchronize do |
|---|
| 152 | @sendq.clear |
|---|
| 153 | end |
|---|
| 154 | end |
|---|
| 155 | end |
|---|
| 156 | |
|---|
| 157 | # flush the TCPSocket |
|---|
| 158 | def flush |
|---|
| 159 | @sock.flush |
|---|
| 160 | end |
|---|
| 161 | |
|---|
| 162 | # Wraps Kernel.select on the socket |
|---|
| 163 | def select(timeout) |
|---|
| 164 | Kernel.select([@sock], nil, nil, timeout) |
|---|
| 165 | end |
|---|
| 166 | |
|---|
| 167 | # shutdown the connection to the server |
|---|
| 168 | def shutdown(how=2) |
|---|
| 169 | @sock.shutdown(how) |
|---|
| 170 | end |
|---|
| 171 | |
|---|
| 172 | private |
|---|
| 173 | |
|---|
| 174 | # same as puts, but expects to be called with a mutex held on @qmutex |
|---|
| 175 | def puts_critical(message) |
|---|
| 176 | # debug "in puts_critical" |
|---|
| 177 | debug "SEND: #{message.inspect}" |
|---|
| 178 | @sock.send(message + "\n",0) |
|---|
| 179 | @last_send = Time.new |
|---|
| 180 | @lines_sent += 1 |
|---|
| 181 | @burst += 1 |
|---|
| 182 | end |
|---|
| 183 | |
|---|
| 184 | end |
|---|
| 185 | |
|---|
| 186 | end |
|---|