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 +8 -0
- data/Manifest.txt +1 -0
- data/README.txt +6 -1
- data/examples/instant_messenger.rb +4 -4
- data/examples/instant_messenger_gui.rb +2 -2
- data/examples/network_status.rb +31 -0
- data/examples/queue_client.rb +2 -2
- data/examples/queue_server.rb +4 -2
- data/lib/journeta/asynchronous.rb +5 -1
- data/lib/journeta/common/dummy_peer_handler.rb +1 -1
- data/lib/journeta/journeta_engine.rb +58 -28
- data/lib/journeta/logger.rb +2 -1
- data/lib/journeta/peer_connection.rb +39 -12
- data/lib/journeta/peer_handler.rb +1 -1
- data/lib/journeta/peer_listener.rb +12 -3
- data/lib/journeta/peer_registry.rb +12 -7
- data/lib/journeta/presence_broadcaster.rb +15 -8
- data/lib/journeta/presence_listener.rb +13 -5
- data/lib/journeta/presence_message.rb +2 -0
- data/lib/journeta/version.rb +2 -2
- data/lib/journeta.rb +1 -0
- data/test/test_event_broadcaster.rb +1 -1
- data/test/test_journeta.rb +1 -1
- data/test/test_lifecycle.rb +1 -1
- data/website/index.html +0 -87
- data/website/index.txt +1 -1
- data/website/stylesheets/screen.css +42 -137
- metadata +3 -2
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
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
|
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::
|
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::
|
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 +
|
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
|
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::
|
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
|
data/examples/queue_client.rb
CHANGED
@@ -9,7 +9,7 @@ include Journeta::Common
|
|
9
9
|
include Journeta::Common::Shutdown
|
10
10
|
|
11
11
|
class JobProcessor
|
12
|
-
def
|
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::
|
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
|
|
data/examples/queue_server.rb
CHANGED
@@ -15,17 +15,19 @@ class JobQueuer
|
|
15
15
|
def initialize(queue)
|
16
16
|
@queue = queue
|
17
17
|
end
|
18
|
-
def
|
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::
|
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
|
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
|
@@ -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
|
9
|
-
|
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
|
-
|
56
|
-
|
57
|
-
# The application message callback handler.
|
58
|
-
@@DEFAULT_PEER_HANDLER = DefaultPeerHandler.new
|
61
|
+
DEFAULT_PEER_PORT = 31338
|
59
62
|
|
60
|
-
|
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
|
-
|
67
|
+
DEFAULT_PRESENCE_NETWORK = '224.220.221.222'
|
64
68
|
|
65
69
|
# The wait time, in seconds, between rebroadcasts of peer presence.
|
66
|
-
|
70
|
+
DEFAULT_PRESENCE_PERIOD = 4
|
67
71
|
|
68
72
|
|
69
|
-
|
70
|
-
|
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
|
-
|
78
|
-
@
|
79
|
-
@
|
80
|
-
|
81
|
-
@
|
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
|
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
|
-
#
|
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
|
-
|
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
|
-
|
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
|
data/lib/journeta/logger.rb
CHANGED
@@ -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
|
-
#
|
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
|
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
|
-
|
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
|
-
|
56
|
-
#
|
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
|
}
|
@@ -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.
|
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
|
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
|
-
#
|
6
|
-
#
|
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,
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
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
|
-
|
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
|
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
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
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
|
-
#
|
10
|
-
|
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
|
62
|
+
putsd "Closing presence listener socket."
|
55
63
|
socket.close
|
56
64
|
end
|
57
65
|
end
|
data/lib/journeta/version.rb
CHANGED
data/lib/journeta.rb
CHANGED
data/test/test_journeta.rb
CHANGED
data/test/test_lifecycle.rb
CHANGED
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 “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, 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’t..</h2>
|
54
|
-
|
55
|
-
|
56
|
-
<ol>
|
57
|
-
<li><b>Work outside the <span class="caps">LAN</span></b>. Presence broadcasts don’t propagate to the internet, and there is no such thing as a “server” 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’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’t know and we don’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 “examples” 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
|
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
|
-
|
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
|
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-
|
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
|