journeta 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
data/History.txt CHANGED
@@ -1,3 +1,15 @@
1
+ == 0.0.3 2008-08-21
2
+
3
+ * Added peer 'groups' support to avoid peer message spamming.
4
+ * Major refactoring to make outbound messages sent asynchonously to the sending thread.
5
+ * Example and documentation updates.
6
+ * Shared network queue example added!
7
+ * examples/queue_client.rb
8
+ * examples/queue_server.rb
9
+ * Renaming..
10
+ * Session -> Peer
11
+ * Event -> Presence
12
+
1
13
  == 0.0.2 2008-08-19
2
14
 
3
15
  * First working RubyForge release:
data/Manifest.txt CHANGED
@@ -5,17 +5,22 @@ README.txt
5
5
  Rakefile
6
6
  lib/journeta.rb
7
7
  examples/instant_messenger.rb
8
+ examples/queue_client.rb
9
+ examples/queue_server.rb
8
10
  lib/journeta/logger.rb
9
11
  lib/journeta/version.rb
10
12
  lib/journeta/asynchronous.rb
11
- lib/journeta/session_listener.rb
12
- lib/journeta/session_handler.rb
13
- lib/journeta/event_broadcaster.rb
14
- lib/journeta/event_listener.rb
15
- lib/journeta/presence_message.rb
13
+ lib/journeta/peer_listener.rb
14
+ lib/journeta/peer_handler.rb
16
15
  lib/journeta/peer_registry.rb
17
16
  lib/journeta/peer_connection.rb
17
+ lib/journeta/presence_broadcaster.rb
18
+ lib/journeta/presence_listener.rb
19
+ lib/journeta/presence_message.rb
18
20
  lib/journeta/journeta_engine.rb
21
+ lib/journeta/common/dummy_peer_handler.rb
22
+ lib/journeta/common/job.rb
23
+ lib/journeta/common/shutdown.rb
19
24
  scripts/txt2html
20
25
  setup.rb
21
26
  test/test_journeta.rb
data/README.txt CHANGED
@@ -9,9 +9,9 @@ requiring no advanced networking knowledge to use.
9
9
  Only core Ruby libraries are required, making the library fairly light. As all data is sent accross
10
10
  the wire in YAML form, any arbitrary Ruby object can be sent to peers, provided they..
11
11
 
12
- * Are running a compatible Journeta version, and
12
+ * Are running a compatible Journeta version.
13
13
  * Have access to the same class definitions if you are sending your own custom objects.
14
- * Do not have a firewall preventing
14
+ * Do not have a firewall preventing network I/O.
15
15
 
16
16
  Journeta uses Ruby threading to manage the asynchonous nature of peer-to-peer I/O.
17
17
  For insight into events internal to the library, start ruby with the `--debug` options.
@@ -17,28 +17,46 @@ end
17
17
 
18
18
  # A message handler will be called by the engine every time a message is received.
19
19
  # This code will be customized for your application-specific needs.
20
- class ExampleHandler < Journeta::DefaultSessionHandler
20
+ class ExampleHandler < Journeta::DefaultPeerHandler
21
21
  def handle(message)
22
- puts "#{message.name.chop}: #{message.text}"
22
+ if message.class == ExampleMessage
23
+ puts "#{message.name.chop}: #{message.text}"
24
+ else
25
+ putsd("Unsupported message type received from peer. (#{message})")
26
+ end
23
27
  end
24
28
  end
25
29
 
26
30
  # Now we'll create an instance of the Journeta P2P engine.
27
- # We'll change the default incoming session port to a
31
+ #
32
+ # :peer_port -- We'll change the default incoming session port to a
28
33
  # pseudo-randomly generated number so multiple instances
29
34
  # may be started on the same machine.
30
35
  #
31
36
  # You'll need to find an unused port if..
32
37
  # (1) you intend to run multiple peers on the same machine, or
33
- # (2) the default port (Journeta::JournetaEngine::DEFAULT_SESSION_PORT)
38
+ # (2) the default port (Journeta::JournetaEngine::DEFAULT_PEER_PORT)
34
39
  # is otherwise already taken on your machine.
35
- session_port = (2048 + rand( 2 ** 8))
36
- journeta = Journeta::JournetaEngine.new(:session_port => session_port, :session_handler => ExampleHandler.new)
40
+ #
41
+ # :peer_handler -- A piece of logic you must specify to process objects sent to you from peers.
42
+ # :groups -- Defines the peer types which care about the objects you broadcast. (Optional: by default, all peers will receive all your object broadcasts.)
43
+ peer_port = (2048 + rand( 2 ** 8))
44
+ journeta = Journeta::JournetaEngine.new(:peer_port => peer_port, :peer_handler => ExampleHandler.new, :groups => ['im_example'])
37
45
 
38
46
 
39
47
  # Let the magic begin!
40
48
  journeta.start
41
49
 
50
+
51
+ # You can use the following helper to automatically stop the given engine when the application is killed with CTRL-C.
52
+ include Journeta::Common::Shutdown
53
+ stop_on_shutdown(journeta)
54
+
55
+ # Alternatively, you can stop the engine manually by calling +JournetaEngine#stop+.
56
+ # Do this before exiting to broadcast a message stating you are going offline as a courtesy to your peers, like so..
57
+ # @journeta.stop
58
+
59
+
42
60
  puts "What's your name?"
43
61
  name = gets
44
62
 
@@ -58,20 +76,19 @@ end
58
76
  # Sit around are watch events at the console until the user hits <enter>
59
77
  puts 'Text you enter here will automatically be shown on peers terminals.'
60
78
  begin
61
- loop do
79
+ loop do
80
+ begin
62
81
  input = gets
63
82
  m = ExampleMessage.new
64
83
  m.name = name
65
84
  m.text = input
66
85
  journeta.send_to_known_peers(m)
67
- end
68
- ensure
86
+ end
87
+ end
69
88
  end
70
89
 
71
- # Please stop the engine when shutting down. This broadcasts a message
72
- # stating you are going offline as a courtesy to your peers.
73
- journeta.stop
90
+
74
91
 
75
92
  # The engine can be restarted and stopped as many times as you'd like.
76
- journeta.start
77
- journeta.stop
93
+ #journeta.start
94
+ #journeta.stop
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env ruby
2
+ current_dir = File.dirname(File.expand_path(__FILE__))
3
+ lib_path = File.join(current_dir, '..', 'lib')
4
+ $LOAD_PATH.unshift lib_path
5
+
6
+ require 'journeta'
7
+ include Journeta
8
+ include Journeta::Common
9
+ include Journeta::Common::Shutdown
10
+
11
+ class JobProcessor
12
+ def handle(msg)
13
+ if msg.class == Job && !msg.submission
14
+ puts "Processing job ##{msg.name} from peer ##{msg.owner}."
15
+ end
16
+ end
17
+ end
18
+
19
+ peer_port = (2048 + rand( 2 ** 8))
20
+ journeta = Journeta::JournetaEngine.new(:peer_port => peer_port, :peer_handler => JobProcessor.new, :groups => ['queue_example'])
21
+ stop_on_shutdown(journeta)
22
+ journeta.start
23
+
24
+
25
+ # Keep creating random jobs.
26
+ puts "Don't forget to start a server at some point! CTRL-C to exit this client."
27
+ while true
28
+ num = rand(1024)
29
+ puts "Creating random job ##{num}."
30
+ job = Job.new
31
+ job.owner = journeta.uuid
32
+ job.name = num
33
+ job.description = 'whatever'
34
+ job.data = 'Anything YAML serializable!'
35
+ job.submission = true
36
+ journeta.send_to_known_peers(job) # All servers will get it.
37
+ sleep 4
38
+ end
@@ -0,0 +1,51 @@
1
+ #!/usr/bin/env ruby
2
+ current_dir = File.dirname(File.expand_path(__FILE__))
3
+ lib_path = File.join(current_dir, '..', 'lib')
4
+ $LOAD_PATH.unshift lib_path
5
+
6
+ require 'thread'
7
+ require 'journeta'
8
+ include Journeta
9
+ include Journeta::Common
10
+ include Journeta::Common::Shutdown
11
+
12
+ @queue = Queue.new
13
+
14
+ class JobQueuer
15
+ def initialize(queue)
16
+ @queue = queue
17
+ end
18
+ def handle(msg)
19
+ if msg.class == Job && msg.submission
20
+ puts "Enqueing job '##{msg.name}' from peer ##{msg.owner}."
21
+ msg.submission = false
22
+ @queue.push msg
23
+ end
24
+ end
25
+ end
26
+
27
+ peer_port = (2048 + rand( 2 ** 8))
28
+ journeta = Journeta::JournetaEngine.new(:peer_port => peer_port, :peer_handler => JobQueuer.new(@queue), :groups => ['queue_example'])
29
+ stop_on_shutdown(journeta)
30
+ journeta.start
31
+
32
+
33
+ puts "Start multiple peers to see the server doing stuff! CTRL-C to stop this server. "
34
+ total = 0
35
+ while true
36
+ job = @queue.pop
37
+ puts "Job found! (#{job.name})"
38
+ all = journeta.known_peers.values
39
+ # Note that this list might already be outdated by the time we reach the next line!
40
+
41
+ if all.size > 0
42
+ # Pick a random client
43
+ worker = all[rand(all.size)]
44
+ puts "Sending to peer ##{worker.uuid}."
45
+ journeta.send_to_peer(worker.uuid, job)
46
+ else
47
+ puts "No workers found :( Will check again soon!"
48
+ @queue.push job
49
+ sleep 4 # Wait for peers to join
50
+ end
51
+ end
data/lib/journeta.rb CHANGED
@@ -1,16 +1,21 @@
1
1
 
2
2
  require 'yaml'
3
+ require 'thread'
3
4
 
4
5
 
5
6
  require 'journeta/logger'
6
7
  require 'journeta/version'
7
8
  require 'journeta/asynchronous'
8
9
 
9
- require 'journeta/session_listener'
10
- require 'journeta/session_handler'
11
- require 'journeta/event_broadcaster'
12
- require 'journeta/event_listener'
10
+ require 'journeta/peer_listener'
11
+ require 'journeta/peer_handler'
12
+ require 'journeta/presence_broadcaster'
13
+ require 'journeta/presence_listener'
13
14
  require 'journeta/presence_message'
14
15
  require 'journeta/peer_registry'
15
16
  require 'journeta/peer_connection'
16
17
  require 'journeta/journeta_engine'
18
+
19
+ require 'journeta/common/job'
20
+ require 'journeta/common/shutdown'
21
+ require 'journeta/common/dummy_peer_handler'
@@ -1,34 +1,65 @@
1
- require 'journeta/logger'
1
+ # Copyright 2007, OpenRain, LLC. All rights reserved.
2
2
 
3
- module Journeta
4
3
 
5
- class Asynchronous
6
-
7
- include Logger
8
-
9
- attr_accessor :thread, :engine
4
+ require 'journeta/logger'
10
5
 
11
- def initialize(engine)
12
- @engine = engine
6
+ module Journeta
7
+
8
+ class Asynchronous
9
+
10
+ include Logger
11
+
12
+ attr_accessor :thread, :engine
13
+
14
+
15
+
16
+ def initialize(engine)
17
+ @engine = engine
18
+ @thread_lock = Mutex.new
19
+ @thread = nil
20
+ end
21
+
22
+
23
+
24
+ # Start the +Thread+ for this instance, iff not already running.
25
+ def start
26
+ @thread_lock.synchronize do
27
+ if @thread
28
+ # Do not restart it.
29
+ else
30
+ putsd "Creating asynchronous thread for I/O: #{self.class.to_s}."
31
+ @thread = Thread.new {
32
+ go
33
+ }
34
+ end
13
35
  end
14
-
15
- def start
16
- putsd "Creating asynchronous thread for I/O: #{self.class.to_s}."
17
- @thread = Thread.new {
18
- go # @engine
19
- }
36
+ end
37
+
38
+ # This method is intentionally not present because it would be of no real value. By the time a boolean is returned, the instance could be in a totally different state.
39
+ # def started
40
+ # stopped = true
41
+ # @thread_lock.synchronize do
42
+ # stopped = @thread.nil?
43
+ # end
44
+ # return stopped
45
+ # end
46
+
47
+ # Stop the +Thread+ associated with this instance, iff not already stopped.
48
+ def stop
49
+ @thread_lock.synchronize do
50
+ if @thread
51
+ Thread.kill(@thread)
52
+ @thread.join
53
+ @thread = nil
54
+ end
20
55
  end
21
-
22
- def stop
23
- Thread.kill(@thread)
24
- @thread.join
25
- @thread = nil
26
- end
27
-
28
- def go #(engine)
29
- raise NotImplementedException
30
- end
31
-
32
- end
33
-
56
+ end
57
+
58
+ # Abstract thread logic method which must be implemented by the sub-class.
59
+ def go
60
+ raise NotImplementedException
61
+ end
62
+
63
+ end
64
+
34
65
  end
@@ -0,0 +1,13 @@
1
+ module Journeta
2
+
3
+ module Common
4
+
5
+ class DummyPeerHandler
6
+ def handle(message)
7
+ # Intentionally ingore all messages.
8
+ end
9
+ end
10
+
11
+ end
12
+
13
+ end
@@ -0,0 +1,15 @@
1
+ module Journeta
2
+
3
+ module Common
4
+
5
+ class Job
6
+ attr_accessor :owner
7
+ attr_accessor :name
8
+ attr_accessor :description
9
+ attr_accessor :data
10
+ attr_accessor :submission
11
+ end
12
+
13
+ end
14
+
15
+ end
@@ -0,0 +1,21 @@
1
+ module Journeta
2
+
3
+ module Common
4
+
5
+ module Shutdown
6
+
7
+ def stop_on_shutdown(engine)
8
+ bye = Proc.new {
9
+ engine.stop
10
+ exit 0
11
+ }
12
+ Signal::trap("HUP", bye)
13
+ Signal::trap("INT", bye)
14
+ Signal::trap("KILL", bye)
15
+ end
16
+
17
+ end
18
+
19
+ end
20
+
21
+ end
@@ -5,87 +5,111 @@
5
5
 
6
6
  module Journeta
7
7
 
8
+ # The primary fascade of the entire +Journeta+ library, which composite a number of objects that may or may not have code running asynchronously to the primary application +Thread+.
8
9
  class JournetaEngine
9
10
 
10
11
  include Logger
11
-
12
+
13
+ # A supposedly universally unique id for this instance.
14
+ attr_reader :uuid
15
+
16
+ # An array of peer network names. Ex: ['OpenRain Test', 'quick_chat_app']
17
+ # An empty array indicates implicit membership in all discovered groups.
18
+ attr_reader :groups
19
+
20
+
12
21
  # Continuously sends out "i'm here" presence messages to the local network
13
- attr_accessor :event_broadcaster
22
+ attr_reader :presence_broadcaster
23
+
14
24
  # continuously listens for "i'm here" presence messages from other peers
15
- attr_accessor :event_listener
16
-
25
+ attr_reader :presence_listener
26
+
27
+ # The UDP port for event broadcast messages.
28
+ attr_reader :presence_port
29
+
30
+ # The UDP network address used for broadcast messages.
31
+ attr_reader :presence_address
32
+
33
+ # The amount of time between presence broadcasts.
34
+ attr_reader :presence_period
35
+
36
+
37
+
17
38
  # Constantly listens for incoming peer sessions
18
- attr_accessor :session_listener
39
+ attr_reader :peer_listener
40
+
19
41
  # Application logic which processes session data.
20
- attr_accessor :session_handler
21
-
42
+ attr_reader :peer_handler
43
+
44
+ # The TCP port used to receive direct peer messages.
45
+ attr_reader :peer_port
46
+
22
47
  # Authoritative peer availability database.
23
- attr_accessor :peer_registry
24
-
25
- # Instance-specific configuration which may be overriden at initialization time by the application
26
- attr_reader :configuration
27
-
28
- # The universally-unique ID of this instance.
29
- attr_reader :uuid
48
+ attr_reader :peer_registry
49
+
50
+
51
+
52
+
53
+
54
+ # Incoming direct peer TCP connections will use this port.
55
+ @@DEFAULT_PEER_PORT = 31338
56
+
57
+ # The application message callback handler.
58
+ @@DEFAULT_PEER_HANDLER = DefaultPeerHandler.new
30
59
 
31
- @@DEFAULT_SESSION_PORT = 31338
32
- @@DEFAULT_EVENT_PORT = 31337
33
- @@DEFAULT_SESSION_HANDLER = DefaultSessionHandler.new
60
+ @@DEFAULT_PRESENCE_PORT = 31337
34
61
 
35
62
  # Addresses 224.0.0.0 through 239.255.255.255 are reserved for multicast messages.
36
- @@DEFAULT_EVENT_NETWORK = '224.220.221.222'
37
- @@DEFAULT_EVENT_PERIOD = 4
63
+ @@DEFAULT_PRESENCE_NETWORK = '224.220.221.222'
64
+
65
+ # The wait time, in seconds, between rebroadcasts of peer presence.
66
+ @@DEFAULT_PRESENCE_PERIOD = 4
38
67
 
39
68
 
40
69
  def initialize(configuration ={})
41
70
  putsd "CON: #{configuration}"
42
- @configuration = Hash.new
43
71
 
44
- # A supposedly universally unique id for this instance. not technically gauranteed but close enough for now.
45
72
  # TODO make guaranteed to be unique.
46
- @configuration[:uuid] = configuration[:uuid] || rand(2 ** 31)
47
- # the tcp port to use for direct peer connections
48
- @configuration[:session_port] = configuration[:session_port] || @@DEFAULT_SESSION_PORT
49
-
50
- @session_handler = configuration[:session_handler] || @@DEFAULT_SESSION_HANDLER
73
+ @uuid = configuration[:uuid] || rand(2 ** 31)
74
+ @groups = configuration[:groups]
51
75
 
52
- # The UDP port for event broadcast messages.
53
- @configuration[:event_port] = configuration[:event_port] || @@DEFAULT_EVENT_PORT
54
-
55
- # The UDP network address used for broadcast messages.
56
- @configuration[:event_address] = configuration[:event_address] || @@DEFAULT_EVENT_NETWORK
57
-
58
- # The delay, in seconds, between presence notification broadcasts.
59
- @configuration[:event_period] = configuration[:event_period] || @@DEFAULT_EVENT_PERIOD
60
76
 
61
- # Initialize sub-components.
62
- @session_listener = Journeta::SessionListener.new self
63
- @event_listener = EventListener.new self
64
- @event_broadcaster = EventBroadcaster.new self
77
+ @peer_port = configuration[:peer_port] || @@DEFAULT_PEER_PORT
78
+ @peer_handler = configuration[:peer_handler] || @@DEFAULT_PEER_HANDLER
79
+ @peer_listener = Journeta::PeerListener.new self
80
+
81
+ @presence_port = configuration[:presence_port] || @@DEFAULT_PRESENCE_PORT
82
+ @presence_address = configuration[:presence_address] || @@DEFAULT_PRESENCE_NETWORK
83
+ @presence_period = configuration[:presence_period] || @@DEFAULT_PRESENCE_PERIOD
84
+ @presence_listener = EventListener.new self
85
+ @presence_broadcaster = EventBroadcaster.new self
86
+
65
87
  @peer_registry = PeerRegistry.new self
66
88
  end
67
89
 
68
90
  def start
69
- # start a session listener first so we don't risk missing a connection attempt
70
- putsd "Starting #{@session_listener.class.to_s}"
71
- @session_listener.start
91
+ # Start a peer listener first so we don't risk missing a connection attempt.
92
+ putsd "Starting #{@peer_listener.class.to_s}"
93
+ @peer_listener.start
72
94
 
73
- # start listening for peer events
74
- putsd "Starting #{@event_listener.class.to_s}"
75
- @event_listener.start
95
+ # Start listening for presence events.
96
+ putsd "Starting #{@presence_listener.class.to_s}"
97
+ @presence_listener.start
76
98
 
77
- # start sending our own events
78
- putsd "Starting #{@event_broadcaster.class.to_s}"
79
- @event_broadcaster.start
99
+ # Start sending our own presence events!
100
+ putsd "Starting #{@presence_broadcaster.class.to_s}"
101
+ @presence_broadcaster.start
80
102
  end
81
103
 
82
104
  def stop
83
- # stop broadcasting events
84
- @event_broadcaster.stop
85
- # stop listener for events
86
- @event_listener.stop
87
- # stop listening for incoming peer sessions
88
- @session_listener.stop
105
+ # Stop broadcasting presence.
106
+ @presence_broadcaster.stop
107
+ # Stop listening for presence events, which prevents new peer registrations
108
+ @presence_listener.stop
109
+ # Stop listening for incoming peer data.
110
+ @peer_listener.stop
111
+ # Forcefull terminate all connections, which may be actively passing data.
112
+ @peer_registry.unregister_all
89
113
  end
90
114
 
91
115
  def send_to_known_peers(payload)
@@ -100,13 +124,17 @@ module Journeta
100
124
 
101
125
  # Returns metadata on all known peers in a hash, keyed by the uuid of each.
102
126
  # A record corresponding to this peer is not included.
103
- def known_peers()
104
- peer_registry.peers # FIXME Returns objects outside of synchronized context.
127
+ def known_peers(all_groups = false)
128
+ peer_registry.all(all_groups)
105
129
  end
106
130
 
107
131
  # Adds (or updates) the given +PeerConnection+.
108
132
  def register_peer(peer)
109
- peer_registry.add(peer)
133
+ peer_registry.register(peer)
134
+ end
135
+
136
+ def unregister_peer(peer)
137
+ peer_registry.unregister(peer)
110
138
  end
111
139
 
112
140
  end
@@ -1,3 +1,6 @@
1
+ # Copyright 2007, OpenRain, LLC. All rights reserved.
2
+
3
+
1
4
  module Journeta
2
5
 
3
6
  module Logger
@@ -1,32 +1,63 @@
1
+ # Copyright 2007, OpenRain, LLC. All rights reserved.
2
+
1
3
  module Journeta
2
4
 
3
5
  # An outgoing message tube. Messages may or may not arrive at the destination, but if they do they'll be in order.
4
- class PeerConnection
6
+ class PeerConnection < Journeta::Asynchronous
5
7
 
6
8
  include Logger
7
9
 
8
10
  attr_accessor :uuid
9
11
  attr_accessor :ip_address
10
- attr_accessor :session_port
12
+ attr_accessor :peer_port
11
13
  attr_accessor :version
12
14
  attr_accessor :created_at
13
15
  attr_accessor :updated_at
16
+ attr_accessor :groups
14
17
 
15
- # Returns whether or not the payload was sent successfully.
18
+ def initialize(engine)
19
+ super(engine)
20
+ @queue = Queue.new
21
+ @settings_lock = Mutex.new
22
+ end
23
+
24
+ # Adds the given payload to the outbound message queue and immediately returns.
16
25
  def send_payload(payload)
17
26
  raise "Don't try to send nil payloads!" if payload.nil?
18
- ok = true
27
+ @queue.push payload
28
+ end
29
+
30
+ def update_settings(other)
31
+ @settings_lock.synchronize do
32
+ self.ip_address = other.ip_address
33
+ self.peer_port = other.peer_port
34
+ self.version = other.version
35
+ self.created_at = other.created_at
36
+ self.updated_at = other.updated_at
37
+ end
38
+ end
39
+
40
+ def go
19
41
  begin
20
- s = TCPSocket.new(ip_address, session_port)
21
- data = YAML::dump(payload)
22
- # pp data
23
- s.send(data , 0)
24
- s.close
42
+ while true
43
+ # TODO Reuse TCP connections between pops!
44
+ payload = @queue.pop
45
+ s = nil
46
+ @settings_lock.synchronize do # To prevent corruption of settings.
47
+ s = TCPSocket.new(ip_address, peer_port)
48
+ end
49
+ data = YAML::dump(payload)
50
+ # pp data
51
+ s.send(data , 0)
52
+ s.close
53
+ end
25
54
  rescue
26
- putsd "Peer #{uuid} has gone away."
27
- ok = false
55
+ putsd "Peer #{uuid} has gone away. Deregistering self."
56
+ # Yeah... kindof wierd, I know.
57
+ Thread.new {
58
+ @engine.unregister_peer(self)
59
+ }
28
60
  end
29
- return ok
30
61
  end
31
62
 
32
63
  end
@@ -1,6 +1,6 @@
1
1
  module Journeta
2
2
 
3
- class DefaultSessionHandler
3
+ class DefaultPeerHandler
4
4
 
5
5
  include Logger
6
6
 
@@ -2,11 +2,11 @@ require 'socket'
2
2
 
3
3
  module Journeta
4
4
 
5
- class SessionListener < Journeta::Asynchronous
5
+ class PeerListener < Journeta::Asynchronous
6
6
 
7
- def go #(engine)
7
+ def go
8
8
  begin
9
- port = engine.configuration[:session_port]
9
+ port = @engine.peer_port
10
10
  socket = TCPServer.new(port)
11
11
  putsd "Listening on port #{port}"
12
12
 
@@ -21,7 +21,7 @@ module Journeta
21
21
  end
22
22
  # pp data
23
23
  msg = YAML::load(data)
24
- h = @engine.session_handler
24
+ h = @engine.peer_handler
25
25
  h.handle msg
26
26
  end
27
27
  end
@@ -9,7 +9,7 @@ module Journeta
9
9
  include Logger
10
10
 
11
11
  # {<:uuid> => PeerConnection}
12
- attr_reader :peers
12
+ # attr_reader :peers
13
13
 
14
14
  def initialize(engine)
15
15
  @engine = engine
@@ -23,7 +23,17 @@ module Journeta
23
23
  end
24
24
  end
25
25
 
26
- def add(peer)
26
+ def all(all_groups = false)
27
+ r = nil
28
+ @mutex.synchronize do
29
+ r = all_do(all_groups)
30
+ end
31
+ return r
32
+ end
33
+
34
+ # Adds a +PeerConnection+ to the registry. It is optional but not necessary to call +PeerConnection#start+ before manually adding it to the registry!
35
+ # If a peer with the same UUID is already registered, the given peer will be stopped and the existing one updated.
36
+ def register(peer)
27
37
  raise "Do not try to register a nil peer!" if peer.nil?
28
38
  raise "You can only add #{PeerConnection} instances to this registry, not #{peer.class}!" unless peer.class == PeerConnection
29
39
  @mutex.synchronize do
@@ -31,40 +41,84 @@ module Journeta
31
41
  existing = @peers[peer.uuid]
32
42
  if existing.nil?
33
43
  putsd "Adding peer #{peer.uuid}."
44
+ peer.start
45
+ @peers[peer.uuid] = peer
34
46
  else
35
47
  putsd "Updating peer #{peer.uuid}."
48
+ peer.stop
49
+ existing.update_settings peer
50
+ end
51
+
52
+ end
53
+ end
54
+
55
+ # Removes a +PeerConnection+ from the registry. If the peer is still broadcasting presence, it will magically become reregistered at some point!
56
+ def unregister(peer)
57
+ return unless !peer.nil? and peer.class == PeerConnection
58
+ @mutex.synchronize do
59
+ peer.stop
60
+ if peer.uuid
61
+ @peers.delete peer.uuid
36
62
  end
37
- @peers[peer.uuid] = peer
38
63
  end
39
64
  end
40
65
 
41
66
  def send_to_known_peers(payload)
42
67
  # Iterate over each currently known peer and stuff the payload into each peers outgoing data queue.
43
68
  @mutex.synchronize do
44
- if peers.count > 0
45
- peers.each do |uuid, conn|
46
- sent = conn.send_payload payload
47
- if !sent
48
- # The peer probably went away, so unregister it.
49
- peers.delete uuid
50
- end
69
+ # Grab all peers in relevant groups.
70
+ group = all_do
71
+ n= group.count
72
+ if n > 0
73
+ putsd "Sending payload to #{n} peers."
74
+ group.each do |uuid, conn|
75
+ conn.send_payload payload
51
76
  end
52
77
  else
53
- putsd 'No peers known to send message to!'
78
+ putsd 'No peers (in relevant groups) to send message to!'
79
+ end
80
+ end
81
+ end
82
+
83
+ # Destroys all active connections.
84
+ def unregister_all
85
+ @mutex.synchronize do
86
+ @peers.each do |uuid, n|
87
+ n.stop
54
88
  end
55
89
  end
56
90
  end
57
91
 
58
92
  def send_to_peer(uuid, payload)
59
- sent = peers[uuid].send_payload(payload)
60
- if !sent
61
- # The peer probably went away, so unregister it.
62
- @mutex.synchronize do
63
- peers.remove uuid
93
+ @mutex.synchronize do
94
+ p = @peers[uuid]
95
+ if p
96
+ p.send_payload(payload)
64
97
  end
65
98
  end
66
99
  end
67
100
 
101
+ protected
102
+
103
+ def all_do(all_groups = false)
104
+ res = nil
105
+ if all_groups
106
+ # Create a new structure to avoid corruption of the original.
107
+ res = Hash.new.update @peers
108
+ else
109
+ res = Hash.new
110
+ @peers.each do |uuid, n|
111
+ n.groups.each do |g|
112
+ if @engine.groups.include?(g)
113
+ res[uuid] = n
114
+ end
115
+ end
116
+ end
117
+ end
118
+ return res
119
+ end
120
+
121
+
68
122
  end
69
123
 
70
124
  end
@@ -0,0 +1,41 @@
1
+ #require 'yaml'
2
+
3
+ require 'journeta/asynchronous'
4
+
5
+
6
+ module Journeta
7
+
8
+ class EventBroadcaster < Journeta::Asynchronous
9
+
10
+ attr_accessor :thread
11
+
12
+ def go #(engine)
13
+ address = @engine.presence_address
14
+ port = @engine.presence_port
15
+ delay = @engine.presence_period
16
+ uuid = @engine.uuid
17
+ peer_port = @engine.peer_port
18
+ groups = @engine.groups
19
+ begin
20
+ socket = UDPSocket.open
21
+ if PLATFORM[/linux/i]
22
+ socket.setsockopt( Socket::SOL_SOCKET, Socket::SO_REUSEADDR, [1].pack("i_") )
23
+ else
24
+ # socket.setsockopt(Socket::IPPROTO_IP, Socket::IP_TTL, [1].pack('i')) # Preston's original config for OS X.
25
+ socket.setsockopt( Socket::SOL_SOCKET, Socket::SO_REUSEPORT, [1].pack("i_") ) # Remi's suggested default.
26
+ end
27
+ loop do
28
+ putsd "Sending presence event."
29
+ note = PresenceMessage.new uuid, peer_port, groups
30
+ socket.send(note.to_yaml, 0, address, port)
31
+ sleep delay
32
+ end
33
+ ensure
34
+ putsd "Closing event broadcaster socket."
35
+ socket.close
36
+ end
37
+ end
38
+
39
+ end
40
+
41
+ end
@@ -10,10 +10,10 @@ module Journeta
10
10
  class EventListener < Journeta::Asynchronous
11
11
 
12
12
 
13
- def go #(engine)
14
- event_address = @engine.configuration[:event_address]
15
- port = @engine.configuration[:event_port]
16
- addresses = IPAddr.new(event_address).hton + IPAddr.new("0.0.0.0").hton
13
+ def go
14
+ presence_address = @engine.presence_address
15
+ port = @engine.presence_port
16
+ addresses = IPAddr.new(presence_address).hton + IPAddr.new("0.0.0.0").hton
17
17
  begin
18
18
  socket = UDPSocket.new
19
19
  # Remember how i said this was fucked up? yeaahhhhhh. i hope you like C.
@@ -29,18 +29,17 @@ module Journeta
29
29
  data, meta = socket.recvfrom 1024
30
30
  Thread.new(data) {
31
31
  event = YAML.load(data)
32
- if event.uuid != @engine.configuration[:uuid]
33
- # putsd "New Event: #{data} #{meta.inspect}"
34
- # Update registry
32
+ if event.uuid != @engine.uuid
35
33
  m = YAML::load(data)
36
- peer = PeerConnection.new
37
- # require 'pp'
38
- # pp m
34
+ peer = PeerConnection.new @engine
39
35
  # Why is this always [2]? Not sure.. they should have returned a hash instead.
40
36
  peer.ip_address = meta[2]
41
- peer.session_port = m.session_port
37
+ peer.peer_port = m.peer_port
42
38
  peer.uuid = m.uuid
43
39
  peer.version = m.version
40
+ peer.groups = m.groups
41
+ # TODO validate peer entry is sane before registering it
42
+ # peer.start
44
43
  @engine.register_peer peer
45
44
  end
46
45
  }
@@ -4,14 +4,16 @@ module Journeta
4
4
 
5
5
  attr_accessor :version
6
6
  attr_accessor :uuid
7
- attr_accessor :session_port
8
- attr_accessor :online
7
+ attr_accessor :peer_port
9
8
 
10
- def initialize(uuid, session_port, online = true)
9
+ # An Array of strings. May be empty but not nil. Ex: ['quick_chat', 'Preston Demo 1']
10
+ attr_accessor :groups
11
+
12
+ def initialize(uuid, peer_port, groups = [])
11
13
  @version = Journeta::VERSION::STRING
12
14
  @uuid = uuid
13
- @session_port = session_port
14
- @online = online
15
+ @peer_port = peer_port
16
+ @groups = groups
15
17
  end
16
18
 
17
19
  end
@@ -2,7 +2,7 @@ module Journeta #:nodoc:
2
2
  module VERSION #:nodoc:
3
3
  MAJOR = 0
4
4
  MINOR = 0
5
- TINY = 2
5
+ TINY = 3
6
6
 
7
7
  STRING = [MAJOR, MINOR, TINY].join('.')
8
8
  end
data/website/index.html CHANGED
@@ -31,12 +31,12 @@
31
31
  <div id="main">
32
32
 
33
33
  <h1>Journeta</h1>
34
- <div id="version" class="clickable" onclick='document.location = "http://rubyforge.org/projects/journeta"; return false'>
35
- <!--p>Get Version</p-->
36
- <a href="http://rubyforge.org/projects/journeta" class="numbers">RubyForge Project</a>
37
- </div>
34
+ <!--div id="version" class="clickable" onclick='document.location = "http://rubyforge.org/projects/journeta"; return false'>
35
+ <p>Get Version</p>
36
+ <a href="http://rubyforge.org/frs/?group_id=4138" class="numbers">sudo gem install journeta</a>
37
+ </div-->
38
38
  <h2>Ruby <span class="caps">P2P</span> for your <span class="caps">LAN</span>, yo.</h2>
39
-
39
+ <a href="http://rubyforge.org/frs/?group_id=4138" class="numbers">[RubyForge Project]</a>
40
40
 
41
41
  <p>Journeta is a dirt simple library for peer discovery and message passing between Ruby applications on a <span class="caps">LAN</span>.</p>
42
42
 
@@ -74,10 +74,10 @@
74
74
  Sweeeeet.</p>
75
75
 
76
76
 
77
- <p>Email <a href="mailto:preston.lee@openrain.com">Preston Lee</a> and read the blog.</p>
78
77
  <p class="coda">
79
- <!--a href="mailto:drnicwilliams@gmail.com">Dr Nic</a>, 26th July 2007<br-->
80
- Theme extended from <a href="http://rb2js.rubyforge.org/">Paul Battley</a>
78
+ <a href="mailto:preston.lee@openrain.com">Preston Email</a>
79
+ <a href="http://prestonlee.com/">Preston Blog</a>
80
+ <a href="http://openrain.com/">Preston's Company</a>
81
81
  </p>
82
82
  </div>
83
83
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: journeta
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Preston Lee
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2008-08-19 00:00:00 -07:00
12
+ date: 2008-08-21 00:00:00 -07:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -42,17 +42,22 @@ files:
42
42
  - Rakefile
43
43
  - lib/journeta.rb
44
44
  - examples/instant_messenger.rb
45
+ - examples/queue_client.rb
46
+ - examples/queue_server.rb
45
47
  - lib/journeta/logger.rb
46
48
  - lib/journeta/version.rb
47
49
  - lib/journeta/asynchronous.rb
48
- - lib/journeta/session_listener.rb
49
- - lib/journeta/session_handler.rb
50
- - lib/journeta/event_broadcaster.rb
51
- - lib/journeta/event_listener.rb
52
- - lib/journeta/presence_message.rb
50
+ - lib/journeta/peer_listener.rb
51
+ - lib/journeta/peer_handler.rb
53
52
  - lib/journeta/peer_registry.rb
54
53
  - lib/journeta/peer_connection.rb
54
+ - lib/journeta/presence_broadcaster.rb
55
+ - lib/journeta/presence_listener.rb
56
+ - lib/journeta/presence_message.rb
55
57
  - lib/journeta/journeta_engine.rb
58
+ - lib/journeta/common/dummy_peer_handler.rb
59
+ - lib/journeta/common/job.rb
60
+ - lib/journeta/common/shutdown.rb
56
61
  - scripts/txt2html
57
62
  - setup.rb
58
63
  - test/test_journeta.rb
@@ -1,36 +0,0 @@
1
- #require 'yaml'
2
-
3
- require 'journeta/asynchronous'
4
-
5
-
6
- module Journeta
7
-
8
- class EventBroadcaster < Journeta::Asynchronous
9
-
10
- attr_accessor :thread
11
-
12
- def go #(engine)
13
- address = @engine.configuration[:event_address]
14
- port = @engine.configuration[:event_port]
15
- delay = @engine.configuration[:event_period]
16
- uuid = @engine.configuration[:uuid]
17
- session_port = @engine.configuration[:session_port]
18
- begin
19
- socket = UDPSocket.open
20
- socket.setsockopt(Socket::IPPROTO_IP, Socket::IP_TTL, [1].pack('i'))
21
- loop do
22
- putsd "Sending presence event."
23
- note = PresenceMessage.new uuid, session_port
24
- socket.send(note.to_yaml, 0, address, port)
25
- sleep delay
26
- end
27
- ensure
28
- putsd "Closing event broadcaster socket."
29
- socket.close
30
- end
31
- end
32
-
33
- end
34
-
35
- end
36
-