ahoy 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,18 @@
1
+ base = File.expand_path("#{File.dirname(__FILE__)}/ahoy")
2
+ libs = %W{chat contact contact_list errors user}
3
+
4
+ libs.each do |lib|
5
+ require "#{base}/#{lib}"
6
+ end
7
+
8
+ module Ahoy
9
+ SERVICE_TYPE = "_presence._tcp"
10
+
11
+ def self.more_coming?(reply)
12
+ reply.flags.to_i & DNSSD::Flags::MoreComing > 0
13
+ end
14
+
15
+ def self.add?(reply)
16
+ reply.flags.to_i & DNSSD::Flags::Add > 0
17
+ end
18
+ end
@@ -0,0 +1,26 @@
1
+ require File.expand_path("#{File.dirname(__FILE__)}/../ahoy")
2
+
3
+ module Ahoy
4
+ class Broadcast
5
+ def initialize(name, location="nowhere", domain="local")
6
+ user = Ahoy::User.new(name, location, domain)
7
+ user.sign_in
8
+ sleep 1
9
+ @chats = user.contacts.map {|contact| user.chat(contact)}
10
+ end
11
+
12
+ def send(message)
13
+ @chats.each do |chat|
14
+ begin
15
+ chat.send(message)
16
+ rescue
17
+ @chats.delete(chat)
18
+ end
19
+ end
20
+ end
21
+
22
+ def close
23
+ @chats.each {|chat| chat.close}
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,58 @@
1
+ require 'rubygems'
2
+ require 'xmpp4r'
3
+ require File.expand_path("#{File.dirname(__FILE__)}/xmpp4r_hack")
4
+
5
+ module Ahoy
6
+ class Chat
7
+ attr_reader :user, :contact, :client
8
+ private :client
9
+
10
+ def initialize(user, contact)
11
+ @user = user
12
+ @contact = contact
13
+ @client = nil
14
+ end
15
+
16
+ # May raise Ahoy::ContactOfflineError
17
+ #
18
+ def start
19
+ user.contact.resolve.getaddrinfo
20
+ connect
21
+ end
22
+
23
+ # May raise Ahoy::ContactOfflineError
24
+ #
25
+ def send(message)
26
+ start unless client
27
+
28
+ message = Jabber::Message.new(contact.name, message)
29
+ message.type = :chat
30
+ begin
31
+ @client.send(message)
32
+ rescue IOError
33
+ connect
34
+ retry
35
+ end
36
+ message
37
+ end
38
+
39
+ def close
40
+ @client.close
41
+ @client = nil
42
+ end
43
+
44
+ private
45
+ def connect
46
+ contact.resolve
47
+
48
+ @client = Jabber::Client.new(Jabber::JID.new(user.name))
49
+ sleep 0.5
50
+ begin
51
+ @client.connect(contact.target, contact.port, user.contact.ip)
52
+ rescue Errno::ECONNREFUSED
53
+ raise Ahoy::ContactOfflineError.new("Contact Offline")
54
+ end
55
+ end
56
+
57
+ end
58
+ end
@@ -0,0 +1,59 @@
1
+ require 'rubygems'
2
+ require 'dnssd'
3
+
4
+ module Ahoy
5
+ class Contact
6
+ attr_reader :name, :domain, :target, :ip, :port, :interface
7
+ attr_accessor :online
8
+
9
+ def initialize(name, domain="local")
10
+ @name = name
11
+ @domain = domain
12
+ @target = nil
13
+ @ip = nil
14
+ @port = nil
15
+ @interface = nil
16
+ @online = true
17
+ end
18
+
19
+ def fullname
20
+ [name, Ahoy::SERVICE_TYPE, domain].join(".")
21
+ end
22
+
23
+ def ==(other)
24
+ other.is_a?(self.class) && other.fullname == fullname
25
+ end
26
+
27
+ def online?
28
+ online
29
+ end
30
+
31
+ def resolve
32
+ service = DNSSD::Service.new
33
+ main = Thread.current
34
+ service.resolve(name, Ahoy::SERVICE_TYPE, domain) do |resolved|
35
+ next if Ahoy::more_coming?(resolved)
36
+ service.stop unless service.stopped?
37
+ @target = resolved.target
38
+ @port = resolved.port
39
+ @interface = resolved.interface
40
+ main.run
41
+ end
42
+ Thread.stop unless service.stopped?
43
+ self
44
+ end
45
+
46
+ def getaddrinfo
47
+ service = DNSSD::Service.new
48
+ main = Thread.current
49
+ service.getaddrinfo(target, DNSSD::Service::IPv4, 0, interface) do |addressed|
50
+ service.stop unless service.stopped?
51
+ @ip = addressed.address
52
+ main.run
53
+ end
54
+ Thread.stop unless service.stopped?
55
+ self
56
+ end
57
+
58
+ end
59
+ end
@@ -0,0 +1,76 @@
1
+ require 'thread'
2
+ require 'weakref'
3
+
4
+ module Ahoy
5
+ class ContactList
6
+ include Enumerable
7
+
8
+ attr_reader :list, :weak_list, :lock, :user
9
+ private :list, :weak_list, :lock
10
+
11
+ def initialize(user)
12
+ @user = user
13
+ @list = []
14
+ @weak_list = []
15
+ @lock = Mutex.new
16
+
17
+ start_browse
18
+ end
19
+
20
+ def each(&block)
21
+ lock.synchronize do
22
+ list.each(&block)
23
+ end
24
+ end
25
+
26
+ private
27
+ def start_browse
28
+ DNSSD.browse(Ahoy::SERVICE_TYPE) do |browsed|
29
+ # next if Ahoy::more_coming?(browsed)
30
+ if Ahoy::add?(browsed) && browsed.name != user.name
31
+ add(Ahoy::Contact.new(browsed.name, browsed.domain))
32
+ elsif Ahoy::add?(browsed)
33
+ user.contact = Ahoy::Contact.new(browsed.name, browsed.domain)
34
+ else
35
+ remove(browsed.fullname)
36
+ end
37
+ end
38
+ end
39
+
40
+ def add(contact)
41
+ lock.synchronize do
42
+ unless list.find {|in_list| contact == in_list}
43
+ contact ||= find_in_weak_list(contact)
44
+ contact.online = true
45
+ list.push(contact)
46
+ end
47
+ end
48
+ end
49
+
50
+ def remove(fullname)
51
+ fullname = fullname.fullname if fullname.respond_to?(:fullname)
52
+ lock.synchronize do
53
+ contact = list.find {|c| c.fullname == fullname}
54
+ if contact
55
+ list.delete(contact)
56
+ contact.online = false
57
+ weak_list.push(WeakRef.new(contact))
58
+ contact
59
+ end
60
+ end
61
+ end
62
+
63
+ def find_in_weak_list(contact)
64
+ existing_contact = nil
65
+ Thread.exclusive do
66
+ GC.disable
67
+ weak_list.select! {|ref| ref.weakref_alive?}
68
+ contact_ref = weak_list.find {|ref| contact == ref}
69
+ existing_contact = contact_ref.__getobj__
70
+ GC.enable
71
+ end
72
+ existing_contact
73
+ end
74
+
75
+ end
76
+ end
@@ -0,0 +1,3 @@
1
+ module Ahoy
2
+ ContactOfflineError = Class.new(StandardError)
3
+ end
@@ -0,0 +1,57 @@
1
+ require 'rubygems'
2
+ require 'dnssd'
3
+
4
+ module Ahoy
5
+ class User
6
+ attr_reader :short_name, :location, :domain, :contacts
7
+ attr_accessor :port, :flags, :interface, :contact
8
+
9
+ def initialize(name, location="nowhere", domain="local")
10
+ @short_name = name
11
+ @location = location
12
+ @domain = domain
13
+
14
+ @contacts = Ahoy::ContactList.new(self)
15
+ @contact = nil
16
+
17
+ @port = 5562
18
+ @flags = 0
19
+ @interface = "en0"
20
+ end
21
+
22
+ def name
23
+ "#{short_name}@#{location}"
24
+ end
25
+
26
+ def sign_in(status="avail", msg=nil)
27
+ @registrar = DNSSD.register(
28
+ name,
29
+ Ahoy::SERVICE_TYPE,
30
+ domain,
31
+ port,
32
+ txt_record(status, msg),
33
+ flags.to_i,
34
+ interface)
35
+ end
36
+
37
+ def contact
38
+ sleep 0.01 until @contact
39
+ @contact
40
+ end
41
+
42
+ def chat(contact)
43
+ Ahoy::Chat.new(self, contact)
44
+ end
45
+
46
+ private
47
+ def txt_record(status, msg)
48
+ DNSSD::TextRecord.new(
49
+ "txtvers" => 1,
50
+ "port.p2pj" => port,
51
+ "status" => status,
52
+ "msg" => msg,
53
+ "1st" => short_name)
54
+ end
55
+
56
+ end
57
+ end
@@ -0,0 +1,41 @@
1
+ require 'rubygems'
2
+ require 'xmpp4r'
3
+
4
+ module Jabber
5
+ class Connection
6
+ def connect(host, port, local_host=nil, local_port=nil)
7
+ @host = host
8
+ @port = port
9
+ # Reset is_tls?, so that it works when reconnecting
10
+ @tls = false
11
+
12
+ Jabber::debuglog("CONNECTING:\n#{@host}:#{@port}, local #{local_host}:#{local_port}")
13
+ @socket = TCPSocket.new(@host, @port, local_host, local_port)
14
+
15
+ # We want to use the old and deprecated SSL protocol (usually on port 5223)
16
+ if @use_ssl
17
+ ssl = OpenSSL::SSL::SSLSocket.new(@socket)
18
+ ssl.connect # start SSL session
19
+ ssl.sync_close = true
20
+ Jabber::debuglog("SSL connection established.")
21
+ @socket = ssl
22
+ end
23
+
24
+ start
25
+
26
+ accept_features
27
+
28
+ @keepaliveThread = Thread.new do
29
+ Thread.current.abort_on_exception = true
30
+ keepalive_loop
31
+ end
32
+ end
33
+ end
34
+
35
+ class Client
36
+ def connect(host, port, local_host=nil, local_port=nil)
37
+ super
38
+ self
39
+ end
40
+ end
41
+ end
metadata ADDED
@@ -0,0 +1,82 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ahoy
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Mat Sadler
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2010-01-27 00:00:00 +00:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: dnssd
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 1.3.1
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: xmpp4r
27
+ type: :runtime
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "="
32
+ - !ruby/object:Gem::Version
33
+ version: "0.5"
34
+ version:
35
+ description: Serverless Messaging using DNSDS/mDNS, XMPP, and Ruby
36
+ email: mat@sourcetagsandcodes.com
37
+ executables: []
38
+
39
+ extensions: []
40
+
41
+ extra_rdoc_files: []
42
+
43
+ files:
44
+ - lib/ahoy/broadcast.rb
45
+ - lib/ahoy/chat.rb
46
+ - lib/ahoy/contact.rb
47
+ - lib/ahoy/contact_list.rb
48
+ - lib/ahoy/errors.rb
49
+ - lib/ahoy/user.rb
50
+ - lib/ahoy/xmpp4r_hack.rb
51
+ - lib/ahoy.rb
52
+ has_rdoc: true
53
+ homepage: http://sourcetagsandcodes.com
54
+ licenses: []
55
+
56
+ post_install_message:
57
+ rdoc_options:
58
+ - --inline-source
59
+ - --charset=UTF-8
60
+ require_paths:
61
+ - lib
62
+ required_ruby_version: !ruby/object:Gem::Requirement
63
+ requirements:
64
+ - - ">="
65
+ - !ruby/object:Gem::Version
66
+ version: "0"
67
+ version:
68
+ required_rubygems_version: !ruby/object:Gem::Requirement
69
+ requirements:
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ version: "0"
73
+ version:
74
+ requirements: []
75
+
76
+ rubyforge_project:
77
+ rubygems_version: 1.3.5
78
+ signing_key:
79
+ specification_version: 3
80
+ summary: Bonjour Chat for Ruby
81
+ test_files: []
82
+