journeta 0.0.5 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/History.txt CHANGED
@@ -1,3 +1,11 @@
1
+ == 0.1.0 2008-09-05
2
+
3
+ * Renamed Journeta::JournetaEngine to Journeta::Engine
4
+ * Added Engine.known_peers
5
+ * Peer handler interface changed from 'handle(event)' to 'call(event)' to support raw Proc objects.
6
+ * examples/network_status.rb
7
+ * Documentation updates.
8
+
1
9
  == 0.0.5 2008-08-22
2
10
 
3
11
  * Documentation updates.
data/Manifest.txt CHANGED
@@ -6,6 +6,7 @@ Rakefile
6
6
  lib/journeta.rb
7
7
  examples/instant_messenger.rb
8
8
  examples/instant_messenger_gui.rb
9
+ examples/network_status.rb
9
10
  examples/queue_client.rb
10
11
  examples/queue_server.rb
11
12
  lib/journeta/logger.rb
data/README.txt CHANGED
@@ -39,7 +39,12 @@ For insight into events internal to the library, start ruby with the `--debug` o
39
39
  sent from the server(s). All nodes automatically find eachother. Try running multiples clients,
40
40
  and then multiple servers. Notice that when you have N servers, each job gets run N times,
41
41
  and not necessarilly by the same client!
42
-
42
+
43
+ examples/network_status.rb
44
+
45
+ Monitors the presence of all peers on the network.
46
+
47
+
43
48
  == Author
44
49
 
45
50
  Preston Lee <preston.lee at openrain d0t com>
@@ -11,7 +11,7 @@ include Journeta::Common
11
11
  # A message handler will be called by the engine every time a message is received.
12
12
  # This code will be customized for your application-specific needs.
13
13
  class ExampleHandler < Journeta::DefaultPeerHandler
14
- def handle(message)
14
+ def call(message)
15
15
  if message.class == BasicMessage
16
16
  puts "#{message.name.chop}: #{message.text}"
17
17
  else
@@ -28,13 +28,13 @@ end
28
28
  #
29
29
  # You'll need to find an unused port if..
30
30
  # (1) you intend to run multiple peers on the same machine, or
31
- # (2) the default port (Journeta::JournetaEngine::DEFAULT_PEER_PORT)
31
+ # (2) the default port (Journeta::Engine::DEFAULT_PEER_PORT)
32
32
  # is otherwise already taken on your machine.
33
33
  #
34
34
  # :peer_handler -- A piece of logic you must specify to process objects sent to you from peers.
35
35
  # :groups -- Defines the peer types which care about the objects you broadcast. (Optional: by default, all peers will receive all your object broadcasts.)
36
36
  peer_port = (2048 + rand( 2 ** 8))
37
- journeta = Journeta::JournetaEngine.new(:peer_port => peer_port, :peer_handler => ExampleHandler.new, :groups => ['im_example'])
37
+ journeta = Journeta::Engine.new(:peer_port => peer_port, :peer_handler => ExampleHandler.new, :groups => ['im_example'])
38
38
 
39
39
 
40
40
  # Let the magic begin!
@@ -45,7 +45,7 @@ journeta.start
45
45
  include Journeta::Common::Shutdown
46
46
  stop_on_shutdown(journeta)
47
47
 
48
- # Alternatively, you can stop the engine manually by calling +JournetaEngine#stop+.
48
+ # Alternatively, you can stop the engine manually by calling +Engine#stop+.
49
49
  # Do this before exiting to broadcast a message stating you are going offline as a courtesy to your peers, like so..
50
50
  # @journeta.stop
51
51
 
@@ -27,7 +27,7 @@ class JournetaGUIHandler
27
27
  @control = control
28
28
  end
29
29
 
30
- def handle(message)
30
+ def call(message)
31
31
  if message.class == BasicMessage
32
32
  text = @control.get_value
33
33
  text.chop!
@@ -106,7 +106,7 @@ class IConvFrame < Wx::Frame
106
106
  panel.set_sizer_and_fit( sizer )
107
107
 
108
108
  peer_port = (2048 + rand( 2 ** 8))
109
- @journeta = Journeta::JournetaEngine.new(:peer_port => peer_port, :peer_handler => JournetaGUIHandler.new(@peer_control), :groups => ['im_example'])
109
+ @journeta = Journeta::Engine.new(:peer_port => peer_port, :peer_handler => JournetaGUIHandler.new(@peer_control), :groups => ['im_example'])
110
110
  @journeta.start
111
111
  end
112
112
 
@@ -0,0 +1,31 @@
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
+ clear = %x{clear} # HACK
12
+
13
+ peer_port = (2048 + rand( 2 ** 8))
14
+ @journeta = Journeta::Engine.new(:peer_port => peer_port)
15
+ @journeta.start
16
+ stop_on_shutdown(@journeta)
17
+
18
+
19
+ begin
20
+ all = @journeta.known_peers
21
+ puts clear
22
+ puts __FILE__
23
+ puts "Displays infromation on all known peers."
24
+ puts "Updated: #{Time.now}"
25
+ puts "\n"
26
+ puts "UUID\t\tVersion\t\tIP Address\t\tPort\t\tDiscovered\t\tUpdated\t\tGroups\n"
27
+ all.keys.sort.each do |uuid|
28
+ puts "#{all[uuid].uuid}\t#{all[uuid].version}\t\t#{all[uuid].ip_address}\t\t#{all[uuid].peer_port}\t\t#{all[uuid].created_at || 'TODO'}\t\t#{all[uuid].updated_at || 'TODO'}\t[#{all[uuid].groups.join(',')}]"
29
+ end
30
+ sleep(0.2)
31
+ end while true
@@ -9,7 +9,7 @@ include Journeta::Common
9
9
  include Journeta::Common::Shutdown
10
10
 
11
11
  class JobProcessor
12
- def handle(msg)
12
+ def call(msg)
13
13
  if msg.class == Job && !msg.submission
14
14
  puts "Processing job ##{msg.name} from peer ##{msg.owner}."
15
15
  end
@@ -17,7 +17,7 @@ class JobProcessor
17
17
  end
18
18
 
19
19
  peer_port = (2048 + rand( 2 ** 8))
20
- journeta = Journeta::JournetaEngine.new(:peer_port => peer_port, :peer_handler => JobProcessor.new, :groups => ['queue_example'])
20
+ journeta = Journeta::Engine.new(:peer_port => peer_port, :peer_handler => JobProcessor.new, :groups => ['queue_example'])
21
21
  stop_on_shutdown(journeta)
22
22
  journeta.start
23
23
 
@@ -15,17 +15,19 @@ class JobQueuer
15
15
  def initialize(queue)
16
16
  @queue = queue
17
17
  end
18
- def handle(msg)
18
+ def call(msg)
19
19
  if msg.class == Job && msg.submission
20
20
  puts "Enqueing job '##{msg.name}' from peer ##{msg.owner}."
21
21
  msg.submission = false
22
22
  @queue.push msg
23
+ else
24
+ puts "Unsupported junk received. Ignoring."
23
25
  end
24
26
  end
25
27
  end
26
28
 
27
29
  peer_port = (2048 + rand( 2 ** 8))
28
- journeta = Journeta::JournetaEngine.new(:peer_port => peer_port, :peer_handler => JobQueuer.new(@queue), :groups => ['queue_example'])
30
+ journeta = Journeta::Engine.new(:peer_port => peer_port, :peer_handler => JobQueuer.new(@queue), :groups => ['queue_example'])
29
31
  stop_on_shutdown(journeta)
30
32
  journeta.start
31
33
 
@@ -1,10 +1,14 @@
1
- # Copyright 2007, OpenRain, LLC. All rights reserved.
1
+ # Copyright © 2007 OpenRain, LLC. All rights reserved.
2
+ #
3
+ # Preston Lee <preston.lee@openrain.com>
2
4
 
3
5
 
4
6
  require 'journeta/logger'
5
7
 
8
+
6
9
  module Journeta
7
10
 
11
+ # See Journeta::Engine
8
12
  class Asynchronous
9
13
 
10
14
  include Logger
@@ -3,7 +3,7 @@ module Journeta
3
3
  module Common
4
4
 
5
5
  class DummyPeerHandler
6
- def handle(message)
6
+ def call(message)
7
7
  # Intentionally ingore all messages.
8
8
  end
9
9
  end
@@ -2,11 +2,17 @@
2
2
  #
3
3
  # Preston Lee <preston.lee@openrain.com>
4
4
 
5
-
5
+ # The root namespace for the entire #Journeta library.
6
+ # See..
7
+ # * Journeta::Engine
8
+ # * http://journeta.rubyforge.org
6
9
  module Journeta
7
10
 
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+.
9
- class JournetaEngine
11
+ # The primary fascade of the entire +Journeta+ library, which composite a number of
12
+ # objects running asynchronously to the primary application +Thread+. Use of this fascade
13
+ # requires a minimal amount of lifecycle management on your part to start and stop the
14
+ # engine at appropriate times. (Usually only at application startup and shutdown, respectively.)
15
+ class Engine
10
16
 
11
17
  include Logger
12
18
 
@@ -52,55 +58,58 @@ module Journeta
52
58
 
53
59
 
54
60
  # 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
61
+ DEFAULT_PEER_PORT = 31338
59
62
 
60
- @@DEFAULT_PRESENCE_PORT = 31337
63
+ # On UDP port on which we send/receive peer presence messages.
64
+ DEFAULT_PRESENCE_PORT = 31337
61
65
 
62
66
  # Addresses 224.0.0.0 through 239.255.255.255 are reserved for multicast messages.
63
- @@DEFAULT_PRESENCE_NETWORK = '224.220.221.222'
67
+ DEFAULT_PRESENCE_NETWORK = '224.220.221.222'
64
68
 
65
69
  # The wait time, in seconds, between rebroadcasts of peer presence.
66
- @@DEFAULT_PRESENCE_PERIOD = 4
70
+ DEFAULT_PRESENCE_PERIOD = 4
67
71
 
68
72
 
69
- def initialize(configuration ={})
70
- putsd "CON: #{configuration}"
71
-
73
+ # Nothing magical. Just creation of internal components and configuration setup.
74
+ def initialize(configuration ={})
72
75
  # TODO make guaranteed to be unique.
73
76
  @uuid = configuration[:uuid] || rand(2 ** 31)
74
77
  @groups = configuration[:groups]
78
+
75
79
 
80
+ @peer_port = configuration[:peer_port] || DEFAULT_PEER_PORT
81
+ @peer_handler = configuration[:peer_handler] || DefaultPeerHandler.new
82
+
83
+ @presence_port = configuration[:presence_port] || DEFAULT_PRESENCE_PORT
84
+ @presence_address = configuration[:presence_address] || DEFAULT_PRESENCE_NETWORK
85
+ @presence_period = configuration[:presence_period] || DEFAULT_PRESENCE_PERIOD
76
86
 
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
-
87
- @peer_registry = PeerRegistry.new self
87
+ # Inversion of Control is used in the following components to allow for some semblance of testing.
88
+ @peer_listener = configuration[:peer_listener] || Journeta::PeerListener.new(self)
89
+ @peer_registry = configuration[:peer_registry] || PeerRegistry.new(self)
90
+ @presence_listener = configuration[:presence_listener] || PresenceListener.new(self)
91
+ @presence_broadcaster = configuration[:presence_broadcaster] || PresenceBroadcaster.new(self)
88
92
  end
89
93
 
94
+
95
+ # Starts sub-comonents which have their own life-cycle requirements.
96
+ # The registry itself does not have a dedication thread, and thus does not need to be started.
90
97
  def start
91
98
  # Start a peer listener first so we don't risk missing a connection attempt.
92
99
  putsd "Starting #{@peer_listener.class.to_s}"
93
100
  @peer_listener.start
94
101
 
95
- # Start listening for presence events.
102
+ # Start listening for peer presence announcements next.
96
103
  putsd "Starting #{@presence_listener.class.to_s}"
97
104
  @presence_listener.start
98
105
 
99
- # Start sending our own presence events!
106
+ # Now that we're all set up, start sending our own presence events
107
+ # so peer can start sending us data!
100
108
  putsd "Starting #{@presence_broadcaster.class.to_s}"
101
109
  @presence_broadcaster.start
102
110
  end
103
111
 
112
+
104
113
  def stop
105
114
  # Stop broadcasting presence.
106
115
  @presence_broadcaster.stop
@@ -108,15 +117,23 @@ module Journeta
108
117
  @presence_listener.stop
109
118
  # Stop listening for incoming peer data.
110
119
  @peer_listener.stop
111
- # Forcefull terminate all connections, which may be actively passing data.
120
+
121
+ # While the registry does not have its own thread, it is in charge of managing
122
+ # +PeerConnection+s which DO have individual threads. This call
123
+ # forcefully terminates all connections, which may or may not be actively passing data.
112
124
  @peer_registry.unregister_all
113
125
  end
114
126
 
127
+ # Sends the given object to all peers in one of the #groups associated with this instance.
128
+ # The object will be marshalled via YAML, so anything the YAML serializer misses won't make it to the other side(s).
129
+ # The return value is undefined.
115
130
  def send_to_known_peers(payload)
116
131
  # Delegate directly.
117
132
  peer_registry.send_to_known_peers(payload)
118
133
  end
119
134
 
135
+ # Send the given object to the peer of the given UUID, if available.
136
+ # The return value is undefined.
120
137
  def send_to_peer(peer_uuid, payload)
121
138
  # Delegate directly.
122
139
  peer_registry.send_to_peer(peer_uuid, payload)
@@ -128,11 +145,24 @@ module Journeta
128
145
  peer_registry.all(all_groups)
129
146
  end
130
147
 
131
- # Adds (or updates) the given +PeerConnection+.
148
+ def known_groups()
149
+ s = Set.new
150
+ self.known_peers(true).each do |uuid, peer|
151
+ s.merge peer.groups
152
+ end
153
+ s.to_a
154
+ end
155
+
156
+ # Adds (or updates) the given +PeerConnection+. If a peer of the same UUID is found,
157
+ # the existing record will be updated and given instance #PeerConnection#stop'd.
158
+ # This prevents pending outbound data from being accidentally dropped.
132
159
  def register_peer(peer)
133
160
  peer_registry.register(peer)
134
161
  end
135
162
 
163
+ # Forcefully unregisters the given #PeerConnection, though this is of limited use
164
+ # since the #PresenceListener will eventually automatically re-register
165
+ # the peers UUID if it's still online.
136
166
  def unregister_peer(peer)
137
167
  peer_registry.unregister(peer)
138
168
  end
@@ -3,9 +3,10 @@
3
3
 
4
4
  module Journeta
5
5
 
6
+ # A silly logging implementation intended for internal use only. Nothing to see here!
6
7
  module Logger
7
8
 
8
- # a thread safe method for printing the given string if and only if debugging is enabled
9
+ # A thread safe method for printing the given string if and only if debugging is enabled.
9
10
  def putsd(message = '(no message)')
10
11
  $stderr.print("DEBUG: #{message}\n") if $DEBUG
11
12
  end
@@ -1,19 +1,29 @@
1
- # Copyright 2007, OpenRain, LLC. All rights reserved.
2
-
1
+ # Copyright © 2007 OpenRain, LLC. All rights reserved.
2
+ #
3
+ # Preston Lee <preston.lee@openrain.com>
3
4
  module Journeta
4
5
 
5
6
  # An outgoing message tube. Messages may or may not arrive at the destination, but if they do they'll be in order.
6
7
  class PeerConnection < Journeta::Asynchronous
7
-
8
+
8
9
  include Logger
9
10
 
11
+ # String
10
12
  attr_accessor :uuid
11
13
  attr_accessor :ip_address
12
- attr_accessor :peer_port
13
14
  attr_accessor :version
15
+
16
+ # An Array of Strings.
17
+ attr_accessor :groups
18
+
19
+ # Time.
14
20
  attr_accessor :created_at
15
21
  attr_accessor :updated_at
16
- attr_accessor :groups
22
+
23
+ # integer.
24
+ attr_accessor :peer_port
25
+
26
+
17
27
 
18
28
  def initialize(engine)
19
29
  super(engine)
@@ -27,16 +37,24 @@ module Journeta
27
37
  @queue.push payload
28
38
  end
29
39
 
40
+ # Updates this instances settings by copying from a provided template.
41
+ # Peers can, in theory, change IP address, port, version and group
42
+ # settings without re-registering in this instance,
43
+ # which would drop all pending outbound data from this connection queue,
44
+ # and the creation and registration of a new connection record.
45
+ # Peer metadata of this instance will be updated from the same fields of the given instance,
46
+ # however, the internal queue of this instance and current thread I/O context will remain unchanged.
30
47
  def update_settings(other)
31
48
  @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
49
+ self.ip_address = other.ip_address if other.ip_address
50
+ self.peer_port = other.peer_port if other.peer_port
51
+ self.version = other.version if other.version
52
+ self.created_at = other.created_at if other.created_at
53
+ self.updated_at = other.updated_at if other.updated_at
37
54
  end
38
55
  end
39
56
 
57
+ # Implementation of abstract parent declaration.
40
58
  def go
41
59
  begin
42
60
  while true
@@ -52,8 +70,17 @@ module Journeta
52
70
  s.close
53
71
  end
54
72
  rescue
55
- putsd "Peer #{uuid} has gone away. Deregistering self."
56
- # Yeah... kindof wierd, I know.
73
+ # Ok, so this is kindof wierd.
74
+ # Unregistering ourselves in our own thread is a paradox becasue
75
+ # we'd end up killing ourselves before the call completes and the unregistration process can finish.
76
+ # The end effect being we could end up with a stopped peer connection which is still registered.
77
+ # So to kill ourselves cleanly, we need to create *another* thread exclusively for the task.
78
+ # If somehow we end up in this block again before our child thread kills us, we're ok to create another one
79
+ # because the registry will just ignore a request to unregister an unknown connection, and it's internally
80
+ # protected against registry corruption with an exclusive lock.
81
+ #
82
+ # Sorry that this is deceptively complicated. It's a design gotcha! :)
83
+ putsd "Peer #{uuid} has gone away. Deregistering in the background."
57
84
  Thread.new {
58
85
  @engine.unregister_peer(self)
59
86
  }
@@ -4,7 +4,7 @@ module Journeta
4
4
 
5
5
  include Logger
6
6
 
7
- def handle(message)
7
+ def call(message)
8
8
  putsd("New message received! #{message}")
9
9
  end
10
10
 
@@ -1,11 +1,19 @@
1
+ # Copyright © 2007 OpenRain, LLC. All rights reserved.
2
+ #
3
+ # Preston Lee <preston.lee@openrain.com>
4
+
1
5
  require 'socket'
2
6
 
3
7
  module Journeta
4
8
 
9
+ # Accepts inbound connections from other peers using TCP.
10
+ # After the peer finishes sending data, the connection is terminated.
11
+ # No data is returned to the sender.
5
12
  class PeerListener < Journeta::Asynchronous
6
13
 
7
14
  def go
8
15
  begin
16
+ # Grab configuration information from the injected object.
9
17
  port = @engine.peer_port
10
18
  socket = TCPServer.new(port)
11
19
  putsd "Listening on port #{port}"
@@ -13,23 +21,24 @@ module Journeta
13
21
  begin
14
22
  loop do
15
23
  session = socket.accept
24
+ # We'll put the actual handling of the new session in the background so we
25
+ # can continue listening for new connections as soon as possible.
16
26
  Thread.new do
17
27
  data = ''
18
28
  # Read every last bit from the socket before passing off to the handler.
19
29
  while more = session.gets
20
30
  data += more
21
31
  end
22
- # pp data
23
32
  msg = YAML::load(data)
24
33
  h = @engine.peer_handler
25
- h.handle msg
34
+ h.call msg
26
35
  end
27
36
  end
28
37
  rescue
29
38
  putsd "Session closed."
30
39
  end
31
40
  ensure
32
- putsd "Closing event listener socket."
41
+ putsd "Closing peer listener socket."
33
42
  # session.close
34
43
  # socket.close
35
44
  end
@@ -2,8 +2,8 @@ require 'thread'
2
2
 
3
3
  module Journeta
4
4
 
5
- # responsible for keeping in-memory metadata on known peers,
6
- # as well as peer tcp connections
5
+ # Responsible for keeping in-memory metadata on known peers in the form of PeerConnections.
6
+ # TODO Add dead peer reaper functionality.
7
7
  class PeerRegistry #<< Journeta::Asynchronous
8
8
 
9
9
  include Logger
@@ -107,11 +107,16 @@ module Journeta
107
107
  res = Hash.new.update @peers
108
108
  else
109
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
110
+ @peers.each do |uuid, peer|
111
+ if peer.groups
112
+ peer.groups.each do |g|
113
+ if @engine.groups.nil? || @engine.groups.include?(g)
114
+ res[uuid] = peer
115
+ end
116
+ end
117
+ else
118
+ # Peer is a member of all groups.
119
+ res[uuid] = peer
115
120
  end
116
121
  end
117
122
  end
@@ -5,11 +5,13 @@ require 'journeta/asynchronous'
5
5
 
6
6
  module Journeta
7
7
 
8
- class EventBroadcaster < Journeta::Asynchronous
8
+ # Spams the local area network with metadata about the local instance.
9
+ # This allows peers to make direct connections back at a later time.
10
+ class PresenceBroadcaster < Journeta::Asynchronous
9
11
 
10
12
  attr_accessor :thread
11
13
 
12
- def go #(engine)
14
+ def go
13
15
  address = @engine.presence_address
14
16
  port = @engine.presence_port
15
17
  delay = @engine.presence_period
@@ -18,12 +20,17 @@ module Journeta
18
20
  groups = @engine.groups
19
21
  begin
20
22
  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
23
+ begin
24
+ if PLATFORM[/linux/i]
25
+ socket.setsockopt( Socket::SOL_SOCKET, Socket::SO_REUSEADDR, [1].pack("i_") )
26
+ else
27
+ # socket.setsockopt(Socket::IPPROTO_IP, Socket::IP_TTL, [1].pack('i')) # Preston's original config for OS X.
28
+ socket.setsockopt( Socket::SOL_SOCKET, Socket::SO_REUSEPORT, [1].pack("i_") ) # Remi's suggested default.
29
+ end
30
+ rescue
31
+ puts "Native socket library not supported on this platform. Please submit a patch! Exiting since this is fatal :("
32
+ exit 1
33
+ end
27
34
  loop do
28
35
  putsd "Sending presence event."
29
36
  note = PresenceMessage.new uuid, peer_port, groups
@@ -6,9 +6,11 @@ require 'journeta/peer_connection'
6
6
 
7
7
  module Journeta
8
8
 
9
- # uses the fucked up socket api to listen for events broadcast from peers
10
- class EventListener < Journeta::Asynchronous
11
-
9
+ # Uses the fucked up Ruby socket API (which isn't really an API)
10
+ # to listen for presence broadcast from peers. Processing of the
11
+ # inbound data is quickly delegated to a background thread to allow the listener
12
+ # to continue responding to inbound traffic as fast as possible.
13
+ class PresenceListener < Journeta::Asynchronous
12
14
 
13
15
  def go
14
16
  presence_address = @engine.presence_address
@@ -43,15 +45,21 @@ module Journeta
43
45
  peer.uuid = m.uuid
44
46
  peer.version = m.version
45
47
  peer.groups = m.groups
48
+
49
+ # We should not start the #PeerConnection before registering because
50
+ # the peer might already be registered. In this case, we'd have wasted a thread,
51
+ # so we'll let the registry handle startup (if it happens at all.)
52
+ #
53
+ # peer.start
54
+
46
55
  # TODO validate peer entry is sane before registering it
47
- # peer.start
48
56
  @engine.register_peer peer
49
57
  end
50
58
  }
51
59
  # putsd "Event received!"
52
60
  end
53
61
  ensure
54
- putsd "Closing event listener socket."
62
+ putsd "Closing presence listener socket."
55
63
  socket.close
56
64
  end
57
65
  end
@@ -1,5 +1,7 @@
1
1
  module Journeta
2
2
 
3
+ # A built-in data structure which gets broadcast across the network
4
+ # for meta-data exchange purposes. A no-frills PORO.
3
5
  class PresenceMessage
4
6
 
5
7
  attr_accessor :version
@@ -1,8 +1,8 @@
1
1
  module Journeta #:nodoc:
2
2
  module VERSION #:nodoc:
3
3
  MAJOR = 0
4
- MINOR = 0
5
- TINY = 5
4
+ MINOR = 1
5
+ TINY = 0
6
6
 
7
7
  STRING = [MAJOR, MINOR, TINY].join('.')
8
8
  end
data/lib/journeta.rb CHANGED
@@ -1,5 +1,6 @@
1
1
 
2
2
  require 'yaml'
3
+ require 'set'
3
4
  require 'thread'
4
5
 
5
6
 
@@ -4,7 +4,7 @@ class TestEventBroadcaster < Test::Unit::TestCase
4
4
 
5
5
 
6
6
  def setup
7
- # @journeta = JournetaEngine.new
7
+ # @journeta = Engine.new
8
8
  # @journeta.start
9
9
  end
10
10
 
@@ -1,6 +1,6 @@
1
1
  require File.dirname(__FILE__) + '/test_helper.rb'
2
2
 
3
- class TestJournetaEngine < Test::Unit::TestCase
3
+ class TestEngine < Test::Unit::TestCase
4
4
 
5
5
  def setup
6
6
  end
@@ -4,7 +4,7 @@ class TestLifecycle < Test::Unit::TestCase
4
4
 
5
5
 
6
6
  def setup
7
- # @journeta = JournetaEngine.new
7
+ # @journeta = Engine.new
8
8
  # @journeta.start
9
9
  end
10
10
 
data/website/index.html CHANGED
@@ -1,87 +0,0 @@
1
- <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
2
- "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
3
- <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
4
- <head>
5
- <link rel="stylesheet" href="stylesheets/screen.css" type="text/css" media="screen" />
6
- <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
7
- <title>
8
- Journeta
9
- </title>
10
- <script src="javascripts/rounded_corners_lite.inc.js" type="text/javascript"></script>
11
- <style>
12
-
13
- </style>
14
- <script type="text/javascript">
15
- window.onload = function() {
16
- settings = {
17
- tl: { radius: 10 },
18
- tr: { radius: 10 },
19
- bl: { radius: 10 },
20
- br: { radius: 10 },
21
- antiAlias: true,
22
- autoPad: true,
23
- validTags: ["div"]
24
- }
25
- var versionBox = new curvyCorners(settings, document.getElementById("version"));
26
- versionBox.applyCornersToAll();
27
- }
28
- </script>
29
- </head>
30
- <body>
31
- <div id="main">
32
-
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/frs/?group_id=4138" class="numbers">sudo gem install journeta</a>
37
- </div-->
38
- <h2>Ruby <span class="caps">P2P</span> for your <span class="caps">LAN</span>, yo.</h2>
39
- <a href="http://rubyforge.org/frs/?group_id=4138" class="numbers">[RubyForge Project]</a>
40
-
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
-
43
-
44
- <h2>Peers</h2>
45
-
46
-
47
- <p>A &#8220;peer&#8221; is represented by a &#8220;start&#8221;ed instance of JournetaEngine in your Ruby application. You&#8217;ll probably want just one, but may run more if it makes sense for your application to logically represent two (or more) peers simultaneously, or want to run multiple instances on the same machine.</p>
48
-
49
-
50
- <p>Any object that can be serialized via <span class="caps">YAML</span> can be sent and received by Journeta. Received messages (a.k.a. objects defined by your application sent from a peer running the same code) are passed into a message handler you provide in an event-based manner. Messages do not come from your applications main thread!</p>
51
-
52
-
53
- <h2>Journeta Is Simple Because It Doesn&#8217;t..</h2>
54
-
55
-
56
- <ol>
57
- <li><b>Work outside the <span class="caps">LAN</span></b>. Presence broadcasts don&#8217;t propagate to the internet, and there is no such thing as a &#8220;server&#8221; in Journeta.</li>
58
- <li><b>Attempt to be secure</b>, as all network messages are sent in text. Messages from peers are implicitly trusted for truthiness.</li>
59
- <li><b>Solve Ruby&#8217;s threading issues</b>, nor dictate your applications thread handling design. Until Ruby uses native threads, high-performance uses of Journeta will likely be limited. Remember that stuff sent to your message handler is not coming from your applications main thread.</li>
60
- <li><b>Provide backwards compatibility</b>, or for the matter, compatibility with anything other than peers of the same Journeta version. No formal messaging specifications here. Different versions of Journeta may or may not be protocol compatible; we don&#8217;t know and we don&#8217;t keep track.</li>
61
- <li><b>Guarantee message delivery</b>, since <span class="caps">UDP</span> is used for some types of communications, and peers are free to go down at any time.</li>
62
- </ol>
63
-
64
- <h2>Installing</h2>
65
-
66
-
67
- <pre syntax="ruby">sudo gem install journeta</pre>
68
-
69
- <h2>The Basics</h2>
70
-
71
-
72
- <p>Several annotated code snippets can be found in the &#8220;examples&#8221; directory of the gem. Open two (or more) consoles and run and following on each for a completely decentralized, zero-configuration-required chat server.
73
- <pre syntax="ruby">ruby examples/instant_messenger.rb</pre>
74
- Sweeeeet.</p>
75
-
76
-
77
- <p class="coda">
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
- </p>
82
- </div>
83
-
84
- <!-- insert site tracking codes here, like Google Urchin -->
85
-
86
- </body>
87
- </html>
data/website/index.txt CHANGED
@@ -6,7 +6,7 @@ Journeta is a library for passing objects between automatically discovered and c
6
6
 
7
7
  h2. Peers
8
8
 
9
- A "peer" is represented by a "start"ed instance of JournetaEngine in your Ruby application. You'll probably want just one, but may run more if it makes sense for your application to logically represent two (or more) peers simultaneously.
9
+ A "peer" is represented by a "start"ed instance of Engine in your Ruby application. You'll probably want just one, but may run more if it makes sense for your application to logically represent two (or more) peers simultaneously.
10
10
 
11
11
  Any object that can be serialized via YAML can be sent and received by Journeta. Received messages (a.k.a. objects defined by your application sent from a peer) are passed into a message handler you define, in an event-based manner. Message do not come from your applications main thread.
12
12
 
@@ -1,138 +1,43 @@
1
- body {
2
- background-color: #E1D1F1;
3
- font-family: "Georgia", sans-serif;
4
- font-size: 16px;
5
- line-height: 1.6em;
6
- padding: 1.6em 0 0 0;
7
- color: #333;
8
- }
9
- h1, h2, h3, h4, h5, h6 {
10
- color: #444;
11
- }
12
- h1 {
13
- font-family: sans-serif;
14
- font-weight: normal;
15
- font-size: 4em;
16
- line-height: 0.8em;
17
- letter-spacing: -0.1ex;
18
- margin: 5px;
19
- }
20
- li {
21
- padding: 0;
22
- margin: 0;
23
- list-style-type: square;
24
- }
25
- a {
26
- color: #5E5AFF;
27
- background-color: #DAC;
28
- font-weight: normal;
29
- text-decoration: underline;
30
- }
31
- blockquote {
32
- font-size: 90%;
33
- font-style: italic;
34
- border-left: 1px solid #111;
35
- padding-left: 1em;
36
- }
37
- .caps {
38
- font-size: 80%;
39
- }
40
-
41
- #main {
42
- width: 45em;
43
- padding: 0;
44
- margin: 0 auto;
45
- }
46
- .coda {
47
- text-align: right;
48
- color: #77f;
49
- font-size: smaller;
50
- }
51
-
52
- table {
53
- font-size: 90%;
54
- line-height: 1.4em;
55
- color: #ff8;
56
- background-color: #111;
57
- padding: 2px 10px 2px 10px;
58
- border-style: dashed;
59
- }
60
-
61
- th {
62
- color: #fff;
63
- }
64
-
65
- td {
66
- padding: 2px 10px 2px 10px;
67
- }
68
-
69
- .success {
70
- color: #0CC52B;
71
- }
72
-
73
- .failed {
74
- color: #E90A1B;
75
- }
76
-
77
- .unknown {
78
- color: #995000;
79
- }
80
- pre, code {
81
- font-family: monospace;
82
- font-size: 90%;
83
- line-height: 1.4em;
84
- color: #ff8;
85
- background-color: #111;
86
- padding: 2px 10px 2px 10px;
87
- }
88
- .comment { color: #aaa; font-style: italic; }
89
- .keyword { color: #eff; font-weight: bold; }
90
- .punct { color: #eee; font-weight: bold; }
91
- .symbol { color: #0bb; }
92
- .string { color: #6b4; }
93
- .ident { color: #ff8; }
94
- .constant { color: #66f; }
95
- .regex { color: #ec6; }
96
- .number { color: #F99; }
97
- .expr { color: #227; }
98
-
99
- #version {
100
- float: right;
101
- text-align: right;
102
- font-family: sans-serif;
103
- font-weight: normal;
104
- background-color: #B3ABFF;
105
- color: #141331;
106
- padding: 15px 20px 10px 20px;
107
- margin: 0 auto;
108
- margin-top: 15px;
109
- border: 3px solid #141331;
110
- }
111
-
112
- #version .numbers {
113
- display: block;
114
- font-size: 4em;
115
- line-height: 0.8em;
116
- letter-spacing: -0.1ex;
117
- margin-bottom: 15px;
118
- }
119
-
120
- #version p {
121
- text-decoration: none;
122
- color: #141331;
123
- background-color: #B3ABFF;
124
- margin: 0;
125
- padding: 0;
126
- }
127
-
128
- #version a {
129
- text-decoration: none;
130
- color: #141331;
131
- background-color: #B3ABFF;
132
- }
133
-
134
- .clickable {
135
- cursor: pointer;
136
- cursor: hand;
137
- }
1
+ /* Copyright 2008 OpenRain, LLC. All rights reserved. */
138
2
 
3
+ body {
4
+ background-color: #ffffff;
5
+ font-family: "Georgia", sans-serif;
6
+ font-size: 16px;
7
+ line-height: 1.6em;
8
+ color: #333;
9
+ background-position: 0px 0px;
10
+ background-repeat: repeat-x;
11
+ background-image: url(../images/content_side.png);
12
+ background-color: #faf3c0;
13
+ }
14
+
15
+ h1, h2, h3 {font-weight: bold;}
16
+ h1 {font-size: 24pt; color: #194c38;}
17
+ h2 {font-size: 18pt; color: #194c38;}
18
+ h3 {color: #697c68;}
19
+ em {color: #ffffff; font-size: 140%;}
20
+ a {color: #fbf5d0; text-decoration: none;}
21
+
22
+ .clear { clear: both;}
23
+ .code { font-family: monospace; margin: 10px; font-size: 10pt; color: #ff8; background-color: #111; }
24
+ .clickable { cursor: pointer; cursor: hand; }
25
+
26
+ #header_side {background-position: 0px 0px;background-repeat: repeat-x;background-image: url(../images/header_side.jpg);float: left;position: absolute;width: 100%;height: 97px;z-index: -1;}
27
+
28
+ #header { background-position: 0px 0px; background-repeat: repeat-x; background-image: url(../images/header.jpg); margin: 0 auto; width: 700px; height: 97px; }
29
+ #header #name {color: #ffffff; font-size: 48pt; padding: 40px 0 0 10px; }
30
+ #header ol {list-style: none; position: absolute; top: 77px; font-size: 10pt;margin-left: 10px;}
31
+ #header li { display: inline; color: #f0f0f0;margin-left:10px;}
32
+
33
+ #content_wrapper { background-position: 0px 0px; background-repeat: repeat-y; background-image: url(../images/content_shadow.png); width: 729px; margin: 0 auto;}
34
+
35
+ #content { width: 700px; background-color: #ffffff; margin-left: 15px; }
36
+ #content ol {list-style-type: none; margin-left: 20px;}
37
+ #content li {margin-top: 10px;}
38
+ #content #blurb { width: 320px; float: right; margin: 10px; clear: none; font-size: 16pt; color: #d8d8d8;}
39
+ #content #guts > div {margin:20px; padding: 10px; }
40
+ #content #guts p {padding-bottom: 20px; display: block;}
41
+ #content #banner { background-position: 0px 0px; background-repeat: no-repeat; background-image: url(../images/banner.jpg); width: 700px; height: 202px;}
42
+
43
+ #sidebar { float: right; width: 300px; margin: 20px; clear: none; background-color: #FDFEEE; padding: 10px;}
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.5
4
+ version: 0.1.0
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-22 00:00:00 -07:00
12
+ date: 2008-09-05 00:00:00 -07:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -43,6 +43,7 @@ files:
43
43
  - lib/journeta.rb
44
44
  - examples/instant_messenger.rb
45
45
  - examples/instant_messenger_gui.rb
46
+ - examples/network_status.rb
46
47
  - examples/queue_client.rb
47
48
  - examples/queue_server.rb
48
49
  - lib/journeta/logger.rb