tkellem 0.8.7 → 0.8.8

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile CHANGED
@@ -1,4 +1,4 @@
1
1
  source "http://rubygems.org"
2
2
 
3
- # Specify your gem's dependencies in ruby-protocol-buffers.gemspec
3
+ # Specify your gem's dependencies in tkellem.gemspec
4
4
  gemspec
data/Rakefile CHANGED
@@ -11,26 +11,3 @@ RSpec::Core::RakeTask.new(:rcov) do |t|
11
11
  t.rcov = true
12
12
  t.rcov_opts = ["--exclude", "spec,gems/,rubygems/"]
13
13
  end
14
-
15
- require 'yard'
16
- YARD::Rake::YardocTask.new(:doc) do |t|
17
- version = Tkellem::VERSION
18
- t.options = ["--title", "tkellem #{version}", "--files", "LICENSE,README.md"]
19
- end
20
-
21
- begin
22
- require 'jeweler'
23
- Jeweler::Tasks.new do |gem|
24
- gem.name = 'tkellem'
25
- gem.summary = 'IRC bouncer with multi-client support'
26
- gem.email = 'brian@codekitchen.net'
27
- gem.homepage = 'http://github.com/codekitchen/tkellem'
28
- gem.authors = ['Brian Palmer']
29
- gem.add_dependency 'eventmachine'
30
- gem.executables = %w(tkellem)
31
- end
32
-
33
- Jeweler::GemcutterTasks.new
34
- rescue LoadError
35
- # om nom nom
36
- end
@@ -183,7 +183,7 @@ class Bouncer
183
183
  @conn = nil
184
184
  @connected = false
185
185
  @connected_at = nil
186
- @active_conns.each { |c,s| c.unbind }
186
+ @active_conns.each { |c,s| c.close_connection }
187
187
  connect!
188
188
  end
189
189
 
@@ -200,19 +200,8 @@ class Bouncer
200
200
  end
201
201
 
202
202
  def connect!
203
- span = @last_connect ? Time.now - @last_connect : 1000
204
- hosts = @network.hosts(true).map { |h| h }
205
-
206
- if span < 5 || hosts.length < 1
207
- EM.add_timer(5) { connect! }
208
- return
209
- end
210
- @last_connect = Time.now
211
- @cur_host = (@cur_host || 0) % hosts.length
212
- host = hosts[@cur_host]
213
- failsafe("connect: #{host}") do
214
- EM.connect(host.address, host.port, IrcServerConnection, self, host.ssl)
215
- end
203
+ @connector ||= IrcServerConnection.connector(self, network)
204
+ @connector.connect!
216
205
  end
217
206
 
218
207
  def ready!
@@ -97,7 +97,7 @@ module BouncerConnection
97
97
  command = msg.command
98
98
  if @state != :auth && command == 'PRIVMSG' && msg.args.first == '-tkellem'
99
99
  msg_tkellem(IrcMessage.new(nil, 'TKELLEM', [msg.args.last]))
100
- elsif command == 'TKELLEM'
100
+ elsif command == 'TKELLEM' || command == 'TK'
101
101
  msg_tkellem(msg)
102
102
  elsif command == 'CAP'
103
103
  # TODO: full support for CAP -- this just gets mobile colloquy connecting
@@ -34,9 +34,11 @@ class IrcMessage < Struct.new(:prefix, :command, :args, :ctcp)
34
34
  # /msg #someroom hey guys
35
35
  def self.parse_client_command(line)
36
36
  return nil unless line[0] == '/'[0]
37
+ if line =~ %r{^/msg\s+(\S+)\s+(.*)$}
38
+ line = "/PRIVMSG #{$1} :#{$2}"
39
+ end
37
40
  msg = parse(line[1..-1])
38
41
  return nil unless msg
39
- msg.command = 'PRIVMSG' if msg.command == 'MSG'
40
42
  msg
41
43
  end
42
44
 
@@ -56,7 +58,7 @@ class IrcMessage < Struct.new(:prefix, :command, :args, :ctcp)
56
58
  line = []
57
59
  line << ":#{prefix}" unless prefix.nil?
58
60
  line << command
59
- ext_arg = args.last if args.last && args.last.match(%r{\s})
61
+ ext_arg = args.last if args.last && args.last.match(%r{^:|\s})
60
62
  line += ext_arg ? args[0...-1] : args
61
63
  if ctcp?
62
64
  line << ":\x01#{ctcp} #{ext_arg}\x01"
@@ -1,5 +1,7 @@
1
- require 'set'
2
1
  require 'eventmachine'
2
+ require 'set'
3
+ require 'socket'
4
+
3
5
  require 'tkellem/irc_message'
4
6
  require 'tkellem/bouncer_connection'
5
7
 
@@ -7,45 +9,115 @@ module Tkellem
7
9
 
8
10
  module IrcServerConnection
9
11
  include EM::Protocols::LineText2
10
- include Tkellem::EasyLogger
11
12
 
12
- def initialize(bouncer, do_ssl)
13
+ def initialize(connection_state, bouncer, do_ssl)
13
14
  set_delimiter "\r\n"
14
15
  @bouncer = bouncer
15
16
  @ssl = do_ssl
17
+ @connection_state = connection_state
18
+ @connected = false
16
19
  end
17
20
 
18
- def post_init
19
- failsafe(:post_init) do
20
- if @ssl
21
+ def connection_completed
22
+ if @ssl
23
+ @bouncer.failsafe(:connection_completed) do
21
24
  @bouncer.debug "starting TLS"
22
25
  # TODO: support strict cert checks
23
26
  start_tls :verify_peer => false
24
- else
25
- ssl_handshake_completed
26
27
  end
28
+ else
29
+ ssl_handshake_completed
27
30
  end
28
31
  end
29
32
 
30
33
  def ssl_handshake_completed
31
- failsafe(:ssl_handshake_completed) do
32
- EM.next_tick { @bouncer.connection_established(self) }
34
+ @bouncer.failsafe(:ssl_handshake_completed) do
35
+ @connected = true
36
+ @bouncer.connection_established(self)
33
37
  end
34
38
  end
35
39
 
36
40
  def receive_line(line)
37
- failsafe(:receive_line) do
38
- trace "from server: #{line}"
41
+ @bouncer.failsafe(:receive_line) do
42
+ @bouncer.trace "from server: #{line}"
39
43
  msg = IrcMessage.parse(line)
40
44
  @bouncer.server_msg(msg)
41
45
  end
42
46
  end
43
47
 
44
48
  def unbind
45
- failsafe(:unbind) do
46
- @bouncer.disconnected!
49
+ @bouncer.failsafe(:unbind) do
50
+ if @connected
51
+ @bouncer.disconnected!
52
+ else
53
+ @bouncer.debug "Connection failed, trying next"
54
+ @connection_state.connect!
55
+ end
47
56
  end
48
57
  end
58
+
59
+ class ConnectionState < Struct.new(:bouncer, :network, :attempted, :getting)
60
+ def initialize(bouncer, network)
61
+ super(bouncer, network, Set.new, false)
62
+ reset
63
+ end
64
+
65
+ def connect!
66
+ raise("already in the process of getting an address") if getting
67
+ self.getting = true
68
+ network.reload
69
+ host_infos = network.hosts.map { |h| h.attributes }
70
+ EM.defer(proc { find_address(host_infos) }, method(:got_address))
71
+ end
72
+
73
+ def reset
74
+ self.attempted.clear
75
+ end
76
+
77
+ # runs in threadpool
78
+ def find_address(hosts)
79
+ candidates = Set.new
80
+ hosts.each do |host|
81
+ Socket.getaddrinfo(host['address'], host['port'], Socket::AF_INET, Socket::SOCK_STREAM, Socket::IPPROTO_TCP).each do |found|
82
+ candidates << [found[3], host['port'], host['ssl']]
83
+ end
84
+ end
85
+
86
+ to_try = candidates.to_a.sort_by { rand }.find { |c| !attempted.include?(c) }
87
+ if to_try.nil?
88
+ # we've tried all possible hosts, start over
89
+ return nil
90
+ end
91
+
92
+ return to_try
93
+ end
94
+
95
+ # back on event thread
96
+ def got_address(to_try)
97
+ self.getting = false
98
+
99
+ if !to_try
100
+ # sleep for a bit and try again
101
+ bouncer.debug "All available addresses failed, sleeping 5s and then trying over"
102
+ reset
103
+ EM.add_timer(5) { connect! }
104
+ return
105
+ end
106
+
107
+ attempted << to_try
108
+ address, port, ssl = to_try
109
+
110
+ bouncer.debug "Connecting to: #{Host.address_string(address, port, ssl)}"
111
+ bouncer.failsafe("connect: #{Host.address_string(address, port, ssl)}") do
112
+ EM.connect(address, port, IrcServerConnection, self, bouncer, ssl)
113
+ end
114
+ end
115
+ end
116
+
117
+ def self.connector(bouncer, network)
118
+ ConnectionState.new(bouncer, network)
119
+ end
120
+
49
121
  end
50
122
 
51
123
  end
@@ -4,6 +4,10 @@ class Host < ActiveRecord::Base
4
4
  belongs_to :network
5
5
 
6
6
  def to_s
7
+ self.class.address_string(address, port, ssl)
8
+ end
9
+
10
+ def self.address_string(address, port, ssl)
7
11
  "#{ssl ? 'ircs' : 'irc'}://#{address}:#{port}"
8
12
  end
9
13
  end
@@ -1,5 +1,6 @@
1
1
  require 'eventmachine'
2
- require 'json'
2
+ require 'active_support/json'
3
+ require 'active_support/ordered_hash'
3
4
  require 'tkellem/irc_message'
4
5
 
5
6
  module Tkellem
@@ -1,3 +1,3 @@
1
1
  module Tkellem
2
- VERSION = "0.8.7"
2
+ VERSION = "0.8.8"
3
3
  end
@@ -22,6 +22,14 @@ describe IrcMessage, ".parse" do
22
22
  line.replay.should == orig
23
23
  end
24
24
 
25
+ it "should parse and replay messages with leading colons" do
26
+ orig = "MSG #myroom ::)"
27
+ line = IrcMessage.parse(orig)
28
+ line.command.should == "MSG"
29
+ line.args.should == ["#myroom", ":)"]
30
+ line.replay.should == orig
31
+ end
32
+
25
33
  it "should parse with no arguments" do
26
34
  line = IrcMessage.parse("AWAY")
27
35
  line.command.should == "AWAY"
@@ -9,7 +9,8 @@ describe Bouncer, "connection" do
9
9
  end
10
10
 
11
11
  def make_server
12
- b = Bouncer.new(NetworkUser.new(:user => User.new(:username => 'speccer'), :network => Network.new))
12
+ network = Network.create!(:hosts => [Host.create!(:address => 'localhost', :port => 4321)], :name => 'test')
13
+ b = Bouncer.new(NetworkUser.create!(:user => User.new(:username => 'speccer'), :network => network))
13
14
  b
14
15
  end
15
16
 
@@ -66,7 +67,7 @@ describe Bouncer, "connection" do
66
67
  network_user
67
68
  @bouncer = $tk_server.bouncers.values.last
68
69
  if opts[:connect]
69
- @server_conn = em(IrcServerConnection).new(@bouncer, false)
70
+ @server_conn = em(IrcServerConnection).new(nil, @bouncer, false)
70
71
  @server_conn.stub!(:send_data)
71
72
  @bouncer.connection_established(@server_conn)
72
73
  @bouncer.send :ready!
@@ -96,7 +97,7 @@ describe Bouncer, "connection" do
96
97
  it "should attempt another nick if the default is taken" do
97
98
  network_user(:nick => 'mynick')
98
99
  bouncer
99
- @server_conn = em(IrcServerConnection).new(@bouncer, false)
100
+ @server_conn = em(IrcServerConnection).new(nil, @bouncer, false)
100
101
  @server_conn.stub!(:send_data)
101
102
  @bouncer.connection_established(@server_conn)
102
103
  @server_conn.should_receive(:send_data).with("NICK mynick_\r\n")
data/tkellem.gemspec CHANGED
@@ -18,13 +18,9 @@ Gem::Specification.new do |s|
18
18
  s.require_paths = ["lib"]
19
19
 
20
20
  s.add_dependency "eventmachine", "~> 0.12.10"
21
- s.add_dependency "daemons", "~> 1.1.0"
22
- s.add_dependency "json"
23
21
  s.add_dependency "activerecord", "~> 3.0.0"
24
22
  s.add_dependency "sqlite3", "~> 1.3.3"
25
23
 
26
24
  s.add_development_dependency "rspec", "~> 2.5"
27
25
  s.add_development_dependency "rcov"
28
- s.add_development_dependency "yard"
29
26
  end
30
-
metadata CHANGED
@@ -2,7 +2,7 @@
2
2
  name: tkellem
3
3
  version: !ruby/object:Gem::Version
4
4
  prerelease:
5
- version: 0.8.7
5
+ version: 0.8.8
6
6
  platform: ruby
7
7
  authors:
8
8
  - Brian Palmer
@@ -10,7 +10,7 @@ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
12
 
13
- date: 2011-06-20 00:00:00 -06:00
13
+ date: 2011-07-01 00:00:00 -06:00
14
14
  default_executable: tkellem
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
@@ -24,83 +24,50 @@ dependencies:
24
24
  version: 0.12.10
25
25
  type: :runtime
26
26
  version_requirements: *id001
27
- - !ruby/object:Gem::Dependency
28
- name: daemons
29
- prerelease: false
30
- requirement: &id002 !ruby/object:Gem::Requirement
31
- none: false
32
- requirements:
33
- - - ~>
34
- - !ruby/object:Gem::Version
35
- version: 1.1.0
36
- type: :runtime
37
- version_requirements: *id002
38
- - !ruby/object:Gem::Dependency
39
- name: json
40
- prerelease: false
41
- requirement: &id003 !ruby/object:Gem::Requirement
42
- none: false
43
- requirements:
44
- - - ">="
45
- - !ruby/object:Gem::Version
46
- version: "0"
47
- type: :runtime
48
- version_requirements: *id003
49
27
  - !ruby/object:Gem::Dependency
50
28
  name: activerecord
51
29
  prerelease: false
52
- requirement: &id004 !ruby/object:Gem::Requirement
30
+ requirement: &id002 !ruby/object:Gem::Requirement
53
31
  none: false
54
32
  requirements:
55
33
  - - ~>
56
34
  - !ruby/object:Gem::Version
57
35
  version: 3.0.0
58
36
  type: :runtime
59
- version_requirements: *id004
37
+ version_requirements: *id002
60
38
  - !ruby/object:Gem::Dependency
61
39
  name: sqlite3
62
40
  prerelease: false
63
- requirement: &id005 !ruby/object:Gem::Requirement
41
+ requirement: &id003 !ruby/object:Gem::Requirement
64
42
  none: false
65
43
  requirements:
66
44
  - - ~>
67
45
  - !ruby/object:Gem::Version
68
46
  version: 1.3.3
69
47
  type: :runtime
70
- version_requirements: *id005
48
+ version_requirements: *id003
71
49
  - !ruby/object:Gem::Dependency
72
50
  name: rspec
73
51
  prerelease: false
74
- requirement: &id006 !ruby/object:Gem::Requirement
52
+ requirement: &id004 !ruby/object:Gem::Requirement
75
53
  none: false
76
54
  requirements:
77
55
  - - ~>
78
56
  - !ruby/object:Gem::Version
79
57
  version: "2.5"
80
58
  type: :development
81
- version_requirements: *id006
59
+ version_requirements: *id004
82
60
  - !ruby/object:Gem::Dependency
83
61
  name: rcov
84
62
  prerelease: false
85
- requirement: &id007 !ruby/object:Gem::Requirement
86
- none: false
87
- requirements:
88
- - - ">="
89
- - !ruby/object:Gem::Version
90
- version: "0"
91
- type: :development
92
- version_requirements: *id007
93
- - !ruby/object:Gem::Dependency
94
- name: yard
95
- prerelease: false
96
- requirement: &id008 !ruby/object:Gem::Requirement
63
+ requirement: &id005 !ruby/object:Gem::Requirement
97
64
  none: false
98
65
  requirements:
99
66
  - - ">="
100
67
  - !ruby/object:Gem::Version
101
68
  version: "0"
102
69
  type: :development
103
- version_requirements: *id008
70
+ version_requirements: *id005
104
71
  description:
105
72
  email:
106
73
  - brian@codekitchen.net