backchannel 0.1.0

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.
@@ -0,0 +1,15 @@
1
+ Gem::Specification.new do |spec|
2
+ spec.name = "backchannel"
3
+ spec.version = "0.1.0"
4
+ spec.platform = Gem::Platform::RUBY
5
+ spec.license = "MIT"
6
+ spec.authors = ["John Pignata"]
7
+ spec.email = ["john@pignata.com"]
8
+ spec.summary = "Multicast-based peer-to-peer text chat"
9
+ spec.description = "Sample application from article posted on http://tx.pignata.com"
10
+ spec.files = `git ls-files`.split("\n")
11
+ spec.executables = %w(backchannel)
12
+ spec.require_paths = ["lib"]
13
+
14
+ spec.required_ruby_version = ">= 1.9.3"
15
+ end
data/bin/backchannel ADDED
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $: << "./lib"
4
+
5
+ if handle = ARGV[0]
6
+ trap(:INT) { abort }
7
+ require "backchannel"
8
+ Backchannel.start(handle)
9
+ else
10
+ abort "Usage: bcc <HANDLE>"
11
+ end
12
+
@@ -0,0 +1,12 @@
1
+ require_relative "backchannel/client"
2
+ require_relative "backchannel/window"
3
+ require_relative "backchannel/message"
4
+
5
+ class Backchannel
6
+ def self.start(handle)
7
+ client = Client.new(handle)
8
+ window = Window.new(client)
9
+
10
+ window.start
11
+ end
12
+ end
@@ -0,0 +1,67 @@
1
+ require "socket"
2
+ require "thread"
3
+ require "ipaddr"
4
+ require "securerandom"
5
+
6
+ class Client
7
+ MULTICAST_ADDR = "224.6.8.11"
8
+ BIND_ADDR = "0.0.0.0"
9
+ PORT = 6811
10
+
11
+ def initialize(handle)
12
+ @handle = handle
13
+ @client_id = SecureRandom.hex(5)
14
+ @listeners = []
15
+ end
16
+
17
+ def add_message_listener(listener)
18
+ listen unless listening?
19
+ @listeners << listener
20
+ end
21
+
22
+ def transmit(content)
23
+ message = Message.new(
24
+ "client_id" => @client_id,
25
+ "handle" => @handle,
26
+ "content" => content
27
+ )
28
+
29
+ socket.send(message.to_json, 0, MULTICAST_ADDR, PORT)
30
+ message
31
+ end
32
+
33
+ private
34
+
35
+ def listen
36
+ socket.bind(BIND_ADDR, PORT)
37
+
38
+ Thread.new do
39
+ loop do
40
+ attributes, _ = socket.recvfrom(1024)
41
+ message = Message.inflate(attributes)
42
+
43
+ unless message.client_id == @client_id
44
+ @listeners.each { |listener| listener.new_message(message) }
45
+ end
46
+ end
47
+ end
48
+
49
+ @listening = true
50
+ end
51
+
52
+ def listening?
53
+ @listening == true
54
+ end
55
+
56
+ def socket
57
+ @socket ||= UDPSocket.open.tap do |socket|
58
+ socket.setsockopt(Socket::IPPROTO_IP, Socket::IP_ADD_MEMBERSHIP, bind_address)
59
+ socket.setsockopt(Socket::IPPROTO_IP, Socket::IP_MULTICAST_TTL, 1)
60
+ socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_REUSEPORT, 1)
61
+ end
62
+ end
63
+
64
+ def bind_address
65
+ IPAddr.new(MULTICAST_ADDR).hton + IPAddr.new(BIND_ADDR).hton
66
+ end
67
+ end
@@ -0,0 +1,20 @@
1
+ require "json"
2
+
3
+ class Message
4
+ attr_reader :client_id, :handle, :content
5
+
6
+ def self.inflate(json)
7
+ attributes = JSON.parse(json)
8
+ new(attributes)
9
+ end
10
+
11
+ def initialize(attributes={})
12
+ @client_id = attributes.fetch("client_id")
13
+ @handle = attributes.fetch("handle")
14
+ @content = attributes.fetch("content")
15
+ end
16
+
17
+ def to_json
18
+ { client_id: client_id, handle: handle, content: content }.to_json
19
+ end
20
+ end
@@ -0,0 +1,81 @@
1
+ require "curses"
2
+
3
+ class Window
4
+ include Curses
5
+
6
+ def initialize(client)
7
+ @client = client
8
+ @messages = []
9
+ end
10
+
11
+ def start
12
+ init_screen
13
+ start_color
14
+ init_pair(COLOR_WHITE, COLOR_BLACK, COLOR_WHITE)
15
+ redraw
16
+
17
+ @client.add_message_listener(self)
18
+
19
+ loop do
20
+ capture_input
21
+ end
22
+ end
23
+
24
+ def new_message(message)
25
+ @messages << message
26
+ redraw
27
+ end
28
+
29
+ private
30
+
31
+ def capture_input
32
+ content = getstr
33
+
34
+ if content.length > 0
35
+ message = @client.transmit(content)
36
+ new_message(message)
37
+ end
38
+ end
39
+
40
+ def redraw
41
+ draw_text_field
42
+ draw_messages
43
+ cursor_to_input_line
44
+ refresh
45
+ end
46
+
47
+ def draw_text_field
48
+ setpos(divider_line, 0)
49
+ attron(color_pair(COLOR_WHITE) | A_NORMAL) do
50
+ addstr(" [backchannel]" + " " * cols)
51
+ end
52
+
53
+ cursor_to_input_line
54
+ clrtoeol
55
+ end
56
+
57
+ def draw_messages
58
+ @messages.last(window_line_size).inject(0) do |line_number, message|
59
+ setpos(line_number, 0)
60
+ clrtoeol
61
+ addstr("<#{message.handle}> #{message.content}")
62
+ line_number + 1
63
+ end
64
+ end
65
+
66
+ def input_line
67
+ lines - 1
68
+ end
69
+
70
+ def divider_line
71
+ lines - 2
72
+ end
73
+
74
+ def window_line_size
75
+ lines - 2
76
+ end
77
+
78
+ def cursor_to_input_line
79
+ setpos(input_line, 0)
80
+ end
81
+ end
metadata ADDED
@@ -0,0 +1,53 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: backchannel
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - John Pignata
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-11-01 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description: Sample application from article posted on http://tx.pignata.com
15
+ email:
16
+ - john@pignata.com
17
+ executables:
18
+ - backchannel
19
+ extensions: []
20
+ extra_rdoc_files: []
21
+ files:
22
+ - backchannel.gemspec
23
+ - bin/backchannel
24
+ - lib/backchannel.rb
25
+ - lib/backchannel/client.rb
26
+ - lib/backchannel/message.rb
27
+ - lib/backchannel/window.rb
28
+ homepage:
29
+ licenses:
30
+ - MIT
31
+ post_install_message:
32
+ rdoc_options: []
33
+ require_paths:
34
+ - lib
35
+ required_ruby_version: !ruby/object:Gem::Requirement
36
+ none: false
37
+ requirements:
38
+ - - ! '>='
39
+ - !ruby/object:Gem::Version
40
+ version: 1.9.3
41
+ required_rubygems_version: !ruby/object:Gem::Requirement
42
+ none: false
43
+ requirements:
44
+ - - ! '>='
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ requirements: []
48
+ rubyforge_project:
49
+ rubygems_version: 1.8.24
50
+ signing_key:
51
+ specification_version: 3
52
+ summary: Multicast-based peer-to-peer text chat
53
+ test_files: []