tkellem 0.8.7 → 0.8.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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