shuttlecraft 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.autotest +25 -0
- data/.gemtest +0 -0
- data/History.txt +6 -0
- data/LICENSE +20 -0
- data/Manifest.txt +20 -0
- data/README.md +76 -0
- data/Rakefile +20 -0
- data/bin/mothership_app +5 -0
- data/bin/shuttlecraft_app +5 -0
- data/lib/shuttlecraft.rb +111 -0
- data/lib/shuttlecraft/comms.rb +79 -0
- data/lib/shuttlecraft/mothership.rb +94 -0
- data/lib/shuttlecraft/mothership_app.rb +46 -0
- data/lib/shuttlecraft/protocol.rb +14 -0
- data/lib/shuttlecraft/resolv.rb +178 -0
- data/lib/shuttlecraft/shuttlecraft_app.rb +164 -0
- data/lib/shuttlecraft/test.rb +43 -0
- data/ringserver.rb +8 -0
- data/test/test_shuttlecraft.rb +124 -0
- data/test/test_shuttlecraft_mothership.rb +97 -0
- data/test/test_shuttlecraft_protocol.rb +18 -0
- metadata +147 -0
data/.autotest
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
|
3
|
+
require "autotest/restart"
|
4
|
+
|
5
|
+
# Autotest.add_hook :initialize do |at|
|
6
|
+
# at.testlib = "minitest/unit"
|
7
|
+
#
|
8
|
+
# at.extra_files << "../some/external/dependency.rb"
|
9
|
+
#
|
10
|
+
# at.libs << ":../some/external"
|
11
|
+
#
|
12
|
+
# at.add_exception "vendor"
|
13
|
+
#
|
14
|
+
# at.add_mapping(/dependency.rb/) do |f, _|
|
15
|
+
# at.files_matching(/test_.*rb$/)
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# %w(TestA TestB).each do |klass|
|
19
|
+
# at.extra_class_map[klass] = "test/test_misc.rb"
|
20
|
+
# end
|
21
|
+
# end
|
22
|
+
|
23
|
+
# Autotest.add_hook :run_command do |at|
|
24
|
+
# system "rake build"
|
25
|
+
# end
|
data/.gemtest
ADDED
File without changes
|
data/History.txt
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2013 Davy Stevenson
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
6
|
+
this software and associated documentation files (the "Software"), to deal in
|
7
|
+
the Software without restriction, including without limitation the rights to
|
8
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
9
|
+
the Software, and to permit persons to whom the Software is furnished to do so,
|
10
|
+
subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
17
|
+
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
18
|
+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
19
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
20
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Manifest.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
.autotest
|
2
|
+
History.txt
|
3
|
+
LICENSE
|
4
|
+
Manifest.txt
|
5
|
+
README.md
|
6
|
+
Rakefile
|
7
|
+
bin/mothership_app
|
8
|
+
bin/shuttlecraft_app
|
9
|
+
lib/shuttlecraft.rb
|
10
|
+
lib/shuttlecraft/mothership.rb
|
11
|
+
lib/shuttlecraft/mothership_app.rb
|
12
|
+
lib/shuttlecraft/protocol.rb
|
13
|
+
lib/shuttlecraft/comms.rb
|
14
|
+
lib/shuttlecraft/resolv.rb
|
15
|
+
lib/shuttlecraft/shuttlecraft_app.rb
|
16
|
+
lib/shuttlecraft/test.rb
|
17
|
+
ringserver.rb
|
18
|
+
test/test_shuttlecraft.rb
|
19
|
+
test/test_shuttlecraft_mothership.rb
|
20
|
+
test/test_shuttlecraft_protocol.rb
|
data/README.md
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
# shuttlecraft
|
2
|
+
|
3
|
+
* http://github.com/davy/shuttlecraft
|
4
|
+
|
5
|
+
## DESCRIPTION:
|
6
|
+
|
7
|
+
Shuttlecraft is an easy-to-use wrapper for much of the functionality in Rinda.
|
8
|
+
|
9
|
+
Create a Shuttlecraft::Mothership to manage the RingServer and RingProvider, and then many Shuttlecrafts can easily connect to the Mothership. Registration management is easy and automatic.
|
10
|
+
|
11
|
+
Easily broadcast messages to all registered services (ie. Shuttlecrafts) from either the Mothership or a particular Shuttlecraft.
|
12
|
+
|
13
|
+
|
14
|
+
## SYNOPSIS:
|
15
|
+
|
16
|
+
Running the apps
|
17
|
+
----------------
|
18
|
+
|
19
|
+
Requires [shoes4](https://github.com/shoes/shoes4). Shoes4 is under active development! Apps tested against commit [7d0a1ee](https://github.com/shoes/shoes4/commit/7d0a1eefea601917dd01419b14ded2812d0acb9f).
|
20
|
+
|
21
|
+
Make sure you are running a ring server (via [RingyDingy](https://github.com/drbrain/RingyDingy))
|
22
|
+
|
23
|
+
$ ring_server
|
24
|
+
|
25
|
+
Run Mothership
|
26
|
+
|
27
|
+
$ path/to/shoes bin/mothership_app
|
28
|
+
|
29
|
+
Run Shuttlecraft
|
30
|
+
|
31
|
+
$ path/to/shoes bin/shuttlecraft_app
|
32
|
+
|
33
|
+
Play!
|
34
|
+
|
35
|
+
* Run multiple Motherships or Shuttlecrafts
|
36
|
+
* Shuttlecraft will ask which Mothership to connect to
|
37
|
+
* Can rescan for Motherships if none are available initially
|
38
|
+
* Broadcast a message to all running Shuttlecrafts registered with a Mothership
|
39
|
+
|
40
|
+
## INSTALL:
|
41
|
+
|
42
|
+
$ gem install shuttlecraft
|
43
|
+
|
44
|
+
## DEVELOPERS:
|
45
|
+
|
46
|
+
After checking out the source, run:
|
47
|
+
|
48
|
+
$ rake newb
|
49
|
+
|
50
|
+
This task will install any missing dependencies, run the tests/specs,
|
51
|
+
and generate the RDoc.
|
52
|
+
|
53
|
+
## LICENSE:
|
54
|
+
|
55
|
+
(The MIT License)
|
56
|
+
|
57
|
+
Copyright (c) 2013 Davy Stevenson, Eric Hodel
|
58
|
+
|
59
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
60
|
+
a copy of this software and associated documentation files (the
|
61
|
+
'Software'), to deal in the Software without restriction, including
|
62
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
63
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
64
|
+
permit persons to whom the Software is furnished to do so, subject to
|
65
|
+
the following conditions:
|
66
|
+
|
67
|
+
The above copyright notice and this permission notice shall be
|
68
|
+
included in all copies or substantial portions of the Software.
|
69
|
+
|
70
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
71
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
72
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
73
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
74
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
75
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
76
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
|
3
|
+
require "rubygems"
|
4
|
+
require "hoe"
|
5
|
+
|
6
|
+
Hoe.plugin :minitest
|
7
|
+
Hoe.plugin :git
|
8
|
+
|
9
|
+
Hoe.spec 'shuttlecraft' do
|
10
|
+
developer 'Davy Stevenson', 'davy.stevenson@gmail.com'
|
11
|
+
developer 'Eric Hodel', 'drbrain@segment7.net'
|
12
|
+
|
13
|
+
extra_deps << ['RingyDingy', '~> 1.6']
|
14
|
+
|
15
|
+
self.readme_file = 'README.md'
|
16
|
+
|
17
|
+
license 'MIT' # this should match the license in the README
|
18
|
+
end
|
19
|
+
|
20
|
+
# vim: syntax=ruby
|
data/bin/mothership_app
ADDED
data/lib/shuttlecraft.rb
ADDED
@@ -0,0 +1,111 @@
|
|
1
|
+
require 'rinda/ring'
|
2
|
+
|
3
|
+
require 'shuttlecraft/comms'
|
4
|
+
|
5
|
+
class Shuttlecraft
|
6
|
+
|
7
|
+
include Shuttlecraft::Comms
|
8
|
+
|
9
|
+
VERSION = '0.0.1'
|
10
|
+
|
11
|
+
# [:name, :Mothership, name, Rinda::TupleSpace:tuplespace]
|
12
|
+
PROVIDER_TEMPLATE = [:name, :Mothership, String, nil]
|
13
|
+
|
14
|
+
# [:name, name, drb_uri]
|
15
|
+
REGISTRATION_TEMPLATE = [:name, String, String]
|
16
|
+
|
17
|
+
attr_accessor :ring_server, :mothership, :name, :protocol
|
18
|
+
|
19
|
+
def initialize(opts={})
|
20
|
+
initialize_comms(opts)
|
21
|
+
|
22
|
+
@drb = DRb.start_service(nil, self)
|
23
|
+
|
24
|
+
@name = opts[:name] || self.class.default_name
|
25
|
+
@protocol = opts[:protocol] || Shuttlecraft::Protocol.default
|
26
|
+
@verbose = opts[:verbose] || false
|
27
|
+
|
28
|
+
@ring_server = Rinda::RingFinger.primary
|
29
|
+
@receive_loop = nil
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.default_name
|
33
|
+
'Shuttlecraft'
|
34
|
+
end
|
35
|
+
|
36
|
+
def provider_template
|
37
|
+
temp = PROVIDER_TEMPLATE.dup
|
38
|
+
temp[1] = @protocol.service_name
|
39
|
+
temp
|
40
|
+
end
|
41
|
+
|
42
|
+
def find_all_motherships
|
43
|
+
ring_server.read_all(provider_template).collect{|_,_,name,ts| {name: name, ts: ts}}
|
44
|
+
end
|
45
|
+
|
46
|
+
def initiate_communication_with_mothership(name=nil)
|
47
|
+
motherships = find_all_motherships
|
48
|
+
|
49
|
+
if name
|
50
|
+
provider = motherships.detect{|m| m[:name] == name}
|
51
|
+
else
|
52
|
+
provider = motherships.first
|
53
|
+
end
|
54
|
+
|
55
|
+
if provider
|
56
|
+
@mothership = Rinda::TupleSpaceProxy.new provider[:ts]
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def register
|
61
|
+
update!
|
62
|
+
unless @mothership.nil? || registered?
|
63
|
+
begin
|
64
|
+
@mothership.write([:name, @name, DRb.uri])
|
65
|
+
rescue DRb::DRbConnError
|
66
|
+
# mothership went away =(
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def registered?
|
72
|
+
update!
|
73
|
+
return false unless @mothership
|
74
|
+
|
75
|
+
!registered_services.detect{|t| !t.nil? && t[0] == @name && t[1] == DRb.uri}.nil?
|
76
|
+
end
|
77
|
+
|
78
|
+
def unregister
|
79
|
+
update!
|
80
|
+
if registered?
|
81
|
+
begin
|
82
|
+
@mothership.take([:name, @name, DRb.uri])
|
83
|
+
rescue DRb::DRbConnError
|
84
|
+
# mothership went away =(
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
private
|
90
|
+
|
91
|
+
##
|
92
|
+
# For Shuttlecraft::Comms
|
93
|
+
def tuplespace
|
94
|
+
@mothership
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
require 'shuttlecraft/mothership'
|
99
|
+
require 'shuttlecraft/protocol'
|
100
|
+
require 'shuttlecraft/mothership_app'
|
101
|
+
require 'shuttlecraft/shuttlecraft_app'
|
102
|
+
|
103
|
+
if __FILE__ == $0
|
104
|
+
s = Shuttlecraft.new
|
105
|
+
s.initate_communication_with_mothership
|
106
|
+
s.register
|
107
|
+
|
108
|
+
sleep(5)
|
109
|
+
|
110
|
+
s.unregister
|
111
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
##
|
2
|
+
# Must define tuplespace to read from
|
3
|
+
# def tuplespace
|
4
|
+
#
|
5
|
+
# Must call initialize_comms inside initialize
|
6
|
+
#
|
7
|
+
class Shuttlecraft
|
8
|
+
module Comms
|
9
|
+
|
10
|
+
def initialize_comms(opts={})
|
11
|
+
@registered_services_ary = []
|
12
|
+
@update_every = opts[:update_every] || 2
|
13
|
+
@last_update = Time.at 0
|
14
|
+
end
|
15
|
+
|
16
|
+
def registered_services
|
17
|
+
update
|
18
|
+
@registered_services_ary
|
19
|
+
end
|
20
|
+
|
21
|
+
##
|
22
|
+
# Registered services are only updatable if they haven't been updated in the
|
23
|
+
# last @update_every seconds. This prevents DRb message spam.
|
24
|
+
def update?
|
25
|
+
(@last_update + @update_every) < Time.now
|
26
|
+
end
|
27
|
+
|
28
|
+
##
|
29
|
+
# Retrieve the last registration data from the TupleSpace.
|
30
|
+
def update
|
31
|
+
return unless update?
|
32
|
+
@last_update = Time.now
|
33
|
+
@registered_services_ary = read_registered_services
|
34
|
+
end
|
35
|
+
|
36
|
+
##
|
37
|
+
# Forces retrieval of registrations from the TupleSpace.
|
38
|
+
def update!
|
39
|
+
@last_update = Time.at 0
|
40
|
+
update
|
41
|
+
end
|
42
|
+
|
43
|
+
##
|
44
|
+
# Enumerates through each registered service's uri
|
45
|
+
def each_service_uri
|
46
|
+
return enum_for __method__ unless block_given?
|
47
|
+
|
48
|
+
registered_services.each do |_, uri|
|
49
|
+
yield uri
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
##
|
54
|
+
# Loops through each client and yields
|
55
|
+
# DRb object for that client
|
56
|
+
def each_client
|
57
|
+
each_service_uri do |uri|
|
58
|
+
begin
|
59
|
+
remote = DRbObject.new_with_uri(uri)
|
60
|
+
yield remote
|
61
|
+
rescue DRb::DRbConnError
|
62
|
+
rescue => e
|
63
|
+
puts "Error sending message to client: #{e.message}" if @verbose
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
def read_registered_services
|
71
|
+
begin
|
72
|
+
tuplespace.read_all(Shuttlecraft::REGISTRATION_TEMPLATE).collect{|_,name,uri| [name,uri]}
|
73
|
+
rescue DRb::DRbConnError
|
74
|
+
[]
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
require 'rinda/ring'
|
2
|
+
require 'rinda/tuplespace'
|
3
|
+
|
4
|
+
class Shuttlecraft::Mothership
|
5
|
+
|
6
|
+
include Shuttlecraft::Comms
|
7
|
+
|
8
|
+
attr_reader :ts, :provider, :name, :protocol
|
9
|
+
|
10
|
+
def initialize(opts={})
|
11
|
+
initialize_comms(opts)
|
12
|
+
|
13
|
+
@drb = DRb.start_service
|
14
|
+
|
15
|
+
@protocol = opts[:protocol] || Shuttlecraft::Protocol.default
|
16
|
+
@name = opts[:name] || @protocol.name
|
17
|
+
@verbose = opts[:verbose] || false
|
18
|
+
|
19
|
+
@ts = Rinda::TupleSpace.new
|
20
|
+
|
21
|
+
renewer = Rinda::SimpleRenewer.new 10
|
22
|
+
|
23
|
+
@provider =
|
24
|
+
Rinda::RingProvider.new(@protocol.service_name, @name, @ts, renewer)
|
25
|
+
@provider.provide
|
26
|
+
|
27
|
+
notify_on_registration
|
28
|
+
notify_on_unregistration
|
29
|
+
notify_on_write
|
30
|
+
end
|
31
|
+
|
32
|
+
|
33
|
+
##
|
34
|
+
# Override this method to add custom registration restrictions
|
35
|
+
def allow_registration?
|
36
|
+
true
|
37
|
+
end
|
38
|
+
|
39
|
+
def notify_on_registration
|
40
|
+
@registration_observer = @ts.notify('write', Shuttlecraft::REGISTRATION_TEMPLATE)
|
41
|
+
Thread.new do
|
42
|
+
@registration_observer.each do |reg|
|
43
|
+
puts "Recieved registration from #{reg[1][1]}" if @verbose
|
44
|
+
if allow_registration?
|
45
|
+
update!
|
46
|
+
send(:on_registration) if respond_to? :on_registration
|
47
|
+
else
|
48
|
+
puts "Registration not allowed" if @verbose
|
49
|
+
@ts.take(reg[1])
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def notify_on_unregistration
|
56
|
+
@unregistration_observer = @ts.notify('take', Shuttlecraft::REGISTRATION_TEMPLATE)
|
57
|
+
Thread.new do
|
58
|
+
@unregistration_observer.each do |reg|
|
59
|
+
puts "Recieved unregistration from #{reg[1][1]}" if @verbose
|
60
|
+
update!
|
61
|
+
send(:on_unregistration) if respond_to? :on_unregistration
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def notify_on_write
|
67
|
+
@write_observer = @ts.notify 'write', [nil]
|
68
|
+
Thread.new do
|
69
|
+
@write_observer.each {|n| p n}
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
##
|
76
|
+
# For Shuttlecraft::Comms
|
77
|
+
def tuplespace
|
78
|
+
@ts
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
|
83
|
+
if __FILE__ == $0
|
84
|
+
m = Shuttlecraft::Mothership.new
|
85
|
+
|
86
|
+
while(true)
|
87
|
+
|
88
|
+
puts "Registered services: #{m.registered_services.join(', ')}"
|
89
|
+
|
90
|
+
sleep(5)
|
91
|
+
end
|
92
|
+
|
93
|
+
DRb.thread.join
|
94
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
class Shuttlecraft::MothershipApp
|
2
|
+
|
3
|
+
def self.run
|
4
|
+
Shoes.app width: 360, height: 360, resizeable: false, title: 'Mothership' do
|
5
|
+
@mothership = nil
|
6
|
+
|
7
|
+
def launch_screen
|
8
|
+
clear do
|
9
|
+
background black
|
10
|
+
title "Build Mothership", stroke: white
|
11
|
+
edit_line text: 'Name' do |s|
|
12
|
+
@name = s.text
|
13
|
+
end
|
14
|
+
button('launch') {
|
15
|
+
@mothership = Shuttlecraft::Mothership.new(name: @name)
|
16
|
+
display_screen
|
17
|
+
}
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def display_screen
|
22
|
+
clear do
|
23
|
+
background "#ffffff"
|
24
|
+
|
25
|
+
stack :margin => 20 do
|
26
|
+
title "Mothership #{@mothership.name}"
|
27
|
+
|
28
|
+
stack do
|
29
|
+
para 'Registered Services:'
|
30
|
+
@registrations = para
|
31
|
+
end
|
32
|
+
end
|
33
|
+
animate(5) { @registrations.replace registrations_text }
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def registrations_text
|
38
|
+
if @mothership
|
39
|
+
@mothership.registered_services.join(', ')
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
launch_screen
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
class Shuttlecraft::Protocol
|
2
|
+
|
3
|
+
attr_reader :service_name, :name
|
4
|
+
|
5
|
+
def initialize(service_name=:Mothership, name='Mothership')
|
6
|
+
@service_name = service_name
|
7
|
+
@name = name
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.default
|
11
|
+
@@default ||= Shuttlecraft::Protocol.new
|
12
|
+
@@default
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,178 @@
|
|
1
|
+
require 'resolv'
|
2
|
+
|
3
|
+
class Resolv
|
4
|
+
|
5
|
+
class DNS
|
6
|
+
class Requester
|
7
|
+
|
8
|
+
remove_method :request
|
9
|
+
|
10
|
+
def request(sender, tout)
|
11
|
+
start = Time.now
|
12
|
+
timelimit = start + tout
|
13
|
+
begin
|
14
|
+
sender.send
|
15
|
+
rescue Errno::EHOSTUNREACH
|
16
|
+
# multi-homed IPv6 may generate this
|
17
|
+
raise ResolvTimeout
|
18
|
+
end
|
19
|
+
while true
|
20
|
+
before_select = Time.now
|
21
|
+
timeout = timelimit - before_select
|
22
|
+
if timeout <= 0
|
23
|
+
raise ResolvTimeout
|
24
|
+
end
|
25
|
+
select_result = IO.select(@socks, nil, nil, timeout)
|
26
|
+
if !select_result
|
27
|
+
after_select = Time.now
|
28
|
+
next if after_select < timelimit
|
29
|
+
raise ResolvTimeout
|
30
|
+
end
|
31
|
+
begin
|
32
|
+
reply, from = recv_reply(select_result[0])
|
33
|
+
rescue Errno::ECONNREFUSED, # GNU/Linux, FreeBSD
|
34
|
+
Errno::ECONNRESET # Windows
|
35
|
+
# No name server running on the server?
|
36
|
+
# Don't wait anymore.
|
37
|
+
raise ResolvTimeout
|
38
|
+
end
|
39
|
+
begin
|
40
|
+
msg = Message.decode(reply)
|
41
|
+
rescue DecodeError
|
42
|
+
next # broken DNS message ignored
|
43
|
+
end
|
44
|
+
if s = sender_for(from, msg)
|
45
|
+
break
|
46
|
+
else
|
47
|
+
# unexpected DNS message ignored
|
48
|
+
end
|
49
|
+
end
|
50
|
+
return msg, s.data
|
51
|
+
end
|
52
|
+
|
53
|
+
def sender_for(addr, msg)
|
54
|
+
@senders[[addr,msg.id]]
|
55
|
+
end
|
56
|
+
|
57
|
+
class MDNSOneShot < UnconnectedUDP # :nodoc:
|
58
|
+
def sender(msg, data, host, port=Port)
|
59
|
+
id = DNS.allocate_request_id(host, port)
|
60
|
+
request = msg.encode
|
61
|
+
request[0,2] = [id].pack('n')
|
62
|
+
sock = @socks_hash[host.index(':') ? "::" : "0.0.0.0"]
|
63
|
+
return @senders[id] =
|
64
|
+
UnconnectedUDP::Sender.new(request, data, sock, host, port)
|
65
|
+
end
|
66
|
+
|
67
|
+
def sender_for(addr, msg)
|
68
|
+
@senders[msg.id]
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
##
|
75
|
+
# Resolv::MDNS is a one-shot Multicast DNS (mDNS) resolver. It blindly
|
76
|
+
# makes queries to the mDNS addresses without understanding anything about
|
77
|
+
# multicast ports.
|
78
|
+
#
|
79
|
+
# Information taken form the following places:
|
80
|
+
#
|
81
|
+
# * RFC 6762
|
82
|
+
|
83
|
+
class MDNS < DNS
|
84
|
+
|
85
|
+
##
|
86
|
+
# Default mDNS Port
|
87
|
+
|
88
|
+
Port = 5353
|
89
|
+
|
90
|
+
##
|
91
|
+
# Default IPv4 mDNS address
|
92
|
+
|
93
|
+
AddressV4 = '224.0.0.251'
|
94
|
+
|
95
|
+
##
|
96
|
+
# Default IPv6 mDNS address
|
97
|
+
|
98
|
+
AddressV6 = 'ff02::fb'
|
99
|
+
|
100
|
+
##
|
101
|
+
# Default mDNS addresses
|
102
|
+
|
103
|
+
Addresses = [
|
104
|
+
[AddressV4, Port],
|
105
|
+
[AddressV6, Port],
|
106
|
+
]
|
107
|
+
|
108
|
+
##
|
109
|
+
# Creates a new one-shot Multicast DNS (mDNS) resolver.
|
110
|
+
#
|
111
|
+
# +config_info+ can be:
|
112
|
+
#
|
113
|
+
# nil::
|
114
|
+
# Uses the default mDNS addresses
|
115
|
+
#
|
116
|
+
# Hash::
|
117
|
+
# Must contain :nameserver or :nameserver_port like
|
118
|
+
# Resolv::DNS#initialize.
|
119
|
+
|
120
|
+
def initialize(config_info=nil)
|
121
|
+
if config_info then
|
122
|
+
super({ nameserver_port: Addresses }.merge(config_info))
|
123
|
+
else
|
124
|
+
super(nameserver_port: Addresses)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
##
|
129
|
+
# Iterates over all IP addresses for +name+ retrieved from the mDNS
|
130
|
+
# resolver, provided name ends with "local". If the name does not end in
|
131
|
+
# "local" no records will be returned.
|
132
|
+
#
|
133
|
+
# +name+ can be a Resolv::DNS::Name or a String. Retrieved addresses will
|
134
|
+
# be a Resolv::IPv4 or Resolv::IPv6
|
135
|
+
|
136
|
+
def each_address(name)
|
137
|
+
name = Resolv::DNS::Name.create(name)
|
138
|
+
|
139
|
+
return unless name.to_a.last == 'local'
|
140
|
+
|
141
|
+
super(name)
|
142
|
+
end
|
143
|
+
|
144
|
+
def make_udp_requester # :nodoc:
|
145
|
+
nameserver_port = @config.nameserver_port
|
146
|
+
Requester::MDNSOneShot.new(*nameserver_port)
|
147
|
+
end
|
148
|
+
|
149
|
+
end
|
150
|
+
|
151
|
+
def DefaultResolver.replace_resolvers new_resolvers
|
152
|
+
@resolvers = new_resolvers
|
153
|
+
end
|
154
|
+
|
155
|
+
end unless Resolv.const_defined? :MDNS
|
156
|
+
|
157
|
+
require 'resolv-replace'
|
158
|
+
require 'tempfile'
|
159
|
+
|
160
|
+
broadcast_hosts = nil
|
161
|
+
|
162
|
+
Tempfile.open 'hosts' do |io|
|
163
|
+
io.puts '255.255.255.255 <broadcast>'
|
164
|
+
|
165
|
+
io.flush
|
166
|
+
|
167
|
+
broadcast_hosts = Resolv::Hosts.new io.path
|
168
|
+
end
|
169
|
+
|
170
|
+
resolvers = [
|
171
|
+
Resolv::MDNS.new,
|
172
|
+
Resolv::Hosts.new,
|
173
|
+
broadcast_hosts,
|
174
|
+
Resolv::DNS.new,
|
175
|
+
]
|
176
|
+
|
177
|
+
Resolv::DefaultResolver.replace_resolvers resolvers
|
178
|
+
|
@@ -0,0 +1,164 @@
|
|
1
|
+
class MyShuttlecraft < Shuttlecraft
|
2
|
+
|
3
|
+
attr_reader :msg_log
|
4
|
+
|
5
|
+
def initialize(opts={})
|
6
|
+
super(opts)
|
7
|
+
@msg_log = []
|
8
|
+
end
|
9
|
+
|
10
|
+
def broadcast(msg)
|
11
|
+
for _, uri in registered_services
|
12
|
+
begin
|
13
|
+
remote = DRbObject.new_with_uri(uri)
|
14
|
+
remote.say(msg, DRb.uri)
|
15
|
+
rescue DRb::DRbConnError
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def say(msg, from)
|
21
|
+
@msg_log << msg
|
22
|
+
begin
|
23
|
+
remote = DRbObject.new_with_uri(from)
|
24
|
+
remote.message_reciept(@name)
|
25
|
+
rescue DRb::DRbConnError
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def message_reciept(from)
|
30
|
+
puts "reciept from #{from}"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
class Shuttlecraft::ShuttlecraftApp
|
35
|
+
|
36
|
+
def self.run
|
37
|
+
@my_app = Shoes.app width: 360, height: 360, resizeable: false, title: 'Shuttlecraft' do
|
38
|
+
|
39
|
+
@shuttlecraft = nil
|
40
|
+
|
41
|
+
def display_screen
|
42
|
+
clear do
|
43
|
+
stack :margin => 20 do
|
44
|
+
title "Shuttlecraft #{@shuttlecraft.name}"
|
45
|
+
|
46
|
+
stack do @status = para end
|
47
|
+
|
48
|
+
@registered = nil
|
49
|
+
@updating_area = stack
|
50
|
+
@msg_stack = stack
|
51
|
+
end
|
52
|
+
|
53
|
+
animate(5) {
|
54
|
+
if @shuttlecraft
|
55
|
+
|
56
|
+
detect_registration_change
|
57
|
+
|
58
|
+
if @registered
|
59
|
+
@registrations.replace registrations_text
|
60
|
+
|
61
|
+
@msg_stack.clear do
|
62
|
+
for msg in @shuttlecraft.msg_log
|
63
|
+
para msg
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
}
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def detect_registration_change
|
73
|
+
if @registered != @shuttlecraft.registered?
|
74
|
+
@registered = @shuttlecraft.registered?
|
75
|
+
@status.replace "#{"Not " unless @registered}Registered"
|
76
|
+
@updating_area.clear do
|
77
|
+
if @registered
|
78
|
+
button("Unregister") { unregister }
|
79
|
+
|
80
|
+
el = edit_line
|
81
|
+
|
82
|
+
button("Send") {
|
83
|
+
@shuttlecraft.broadcast(el.text)
|
84
|
+
el.text = ''
|
85
|
+
}
|
86
|
+
stack do
|
87
|
+
para 'Registered Services:'
|
88
|
+
@registrations = para
|
89
|
+
end
|
90
|
+
else
|
91
|
+
button("Register") { register }
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def launch_screen
|
98
|
+
clear do
|
99
|
+
background black
|
100
|
+
title "Build Shuttlecraft", stroke: white
|
101
|
+
edit_line text: 'Name' do |s|
|
102
|
+
@name = s.text
|
103
|
+
end
|
104
|
+
button('launch') {
|
105
|
+
@shuttlecraft = MyShuttlecraft.new(name: @name)
|
106
|
+
initiate_comms_screen
|
107
|
+
}
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def initiate_comms_screen
|
112
|
+
clear do
|
113
|
+
background black
|
114
|
+
title "Initiate Comms", stroke: white
|
115
|
+
|
116
|
+
stack do
|
117
|
+
motherships = @shuttlecraft.find_all_motherships
|
118
|
+
|
119
|
+
if motherships.empty?
|
120
|
+
subtitle "No Motherships within range", stroke: white
|
121
|
+
else
|
122
|
+
subtitle "Select Mothership", stroke: white
|
123
|
+
end
|
124
|
+
for mothership in motherships
|
125
|
+
button(mothership[:name]) {|b|
|
126
|
+
begin
|
127
|
+
@shuttlecraft.initiate_communication_with_mothership(b.text)
|
128
|
+
rescue
|
129
|
+
initiate_comms_screen
|
130
|
+
end
|
131
|
+
display_screen
|
132
|
+
}
|
133
|
+
end
|
134
|
+
|
135
|
+
button('launch mothership') {
|
136
|
+
load File.dirname(__FILE__) + '/mothership_app.rb'
|
137
|
+
}
|
138
|
+
button('rescan') {
|
139
|
+
initiate_comms_screen
|
140
|
+
}
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def register
|
146
|
+
@shuttlecraft.register if @shuttlecraft
|
147
|
+
end
|
148
|
+
|
149
|
+
def unregister
|
150
|
+
@shuttlecraft.unregister if @shuttlecraft
|
151
|
+
end
|
152
|
+
|
153
|
+
def registrations_text
|
154
|
+
if @shuttlecraft
|
155
|
+
@shuttlecraft.registered_services.join(', ')
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
launch_screen
|
160
|
+
end
|
161
|
+
ensure
|
162
|
+
@my_app.unregister if @my_app
|
163
|
+
end
|
164
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'minitest/autorun'
|
2
|
+
require 'shuttlecraft'
|
3
|
+
|
4
|
+
class Shuttlecraft::Test < MiniTest::Unit::TestCase
|
5
|
+
|
6
|
+
class StubRingServer
|
7
|
+
def initialize; end
|
8
|
+
def read(*args); return 'foo'; end
|
9
|
+
def write(*args); return 'foo'; end
|
10
|
+
end
|
11
|
+
|
12
|
+
class StubMothership
|
13
|
+
|
14
|
+
def initialize
|
15
|
+
@tuples = []
|
16
|
+
end
|
17
|
+
|
18
|
+
def write(*args)
|
19
|
+
@tuples << args
|
20
|
+
end
|
21
|
+
|
22
|
+
def take(*args)
|
23
|
+
@tuples.delete(args)
|
24
|
+
end
|
25
|
+
|
26
|
+
def read_all(template)
|
27
|
+
@tuples.map{|t,r| t}.select do |t|
|
28
|
+
template[1].nil? or template[1] === t[1]
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
class Rinda::RingFinger
|
35
|
+
class << self
|
36
|
+
remove_method :primary
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.primary
|
40
|
+
Shuttlecraft::Test::StubRingServer.new
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
data/ringserver.rb
ADDED
@@ -0,0 +1,124 @@
|
|
1
|
+
require 'shuttlecraft/test'
|
2
|
+
|
3
|
+
class Shuttlecraft::Override < Shuttlecraft
|
4
|
+
def self.default_name
|
5
|
+
'Override'
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
class TestShuttlecraft < Shuttlecraft::Test
|
10
|
+
|
11
|
+
def setup
|
12
|
+
@shuttlecraft = Shuttlecraft.new(name: 'Galileo')
|
13
|
+
@shuttlecraft.mothership = Shuttlecraft::Test::StubMothership.new
|
14
|
+
@stub_mothership = @shuttlecraft.mothership
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_initialization
|
18
|
+
assert_equal false, @shuttlecraft.registered?
|
19
|
+
assert_equal 'Galileo', @shuttlecraft.name
|
20
|
+
assert_equal :Mothership, @shuttlecraft.protocol.service_name
|
21
|
+
assert_equal [], @shuttlecraft.registered_services
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_empty_initialization
|
25
|
+
@shuttlecraft = Shuttlecraft.new
|
26
|
+
assert_equal 'Shuttlecraft', @shuttlecraft.name
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_default_name_override
|
30
|
+
@shuttlecraft = Shuttlecraft::Override.new
|
31
|
+
assert_equal 'Override', @shuttlecraft.name
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_each_service_uri
|
35
|
+
@shuttlecraft.registered_services << ['name', DRb.uri]
|
36
|
+
|
37
|
+
e = @shuttlecraft.each_service_uri
|
38
|
+
|
39
|
+
assert_equal DRb.uri, e.next
|
40
|
+
|
41
|
+
uris = []
|
42
|
+
|
43
|
+
@shuttlecraft.each_service_uri do |uri|
|
44
|
+
uris << uri
|
45
|
+
end
|
46
|
+
|
47
|
+
refute_empty uris
|
48
|
+
end
|
49
|
+
|
50
|
+
def test_each_client
|
51
|
+
@shuttlecraft.registered_services << ['name', 'druby://localhost:1234']
|
52
|
+
|
53
|
+
@shuttlecraft.each_client do |client_obj|
|
54
|
+
assert (DRbObject === client_obj)
|
55
|
+
assert_equal 'druby://localhost:1234', client_obj.__drburi
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def test_uses_default_protocol
|
60
|
+
assert_equal Shuttlecraft::Protocol.default, @shuttlecraft.protocol
|
61
|
+
end
|
62
|
+
|
63
|
+
def test_update_eh
|
64
|
+
assert @shuttlecraft.update?
|
65
|
+
|
66
|
+
@shuttlecraft.update
|
67
|
+
|
68
|
+
refute @shuttlecraft.update?
|
69
|
+
end
|
70
|
+
|
71
|
+
def test_update
|
72
|
+
make_registrations(%w[Davy Eric])
|
73
|
+
|
74
|
+
assert @shuttlecraft.update
|
75
|
+
refute @shuttlecraft.update
|
76
|
+
|
77
|
+
assert_equal %w[Davy Eric], @shuttlecraft.registered_services_ary.collect{|n,u| n}.sort
|
78
|
+
end
|
79
|
+
|
80
|
+
def test_update_bang
|
81
|
+
make_registrations(%w[Davy Eric])
|
82
|
+
assert @shuttlecraft.update
|
83
|
+
|
84
|
+
assert_equal %w[Davy Eric], @shuttlecraft.registered_services.collect{|n,u| n}.sort
|
85
|
+
|
86
|
+
make_registrations(%w[Davy Eric Rein])
|
87
|
+
assert @shuttlecraft.update!
|
88
|
+
|
89
|
+
assert_equal %w[Davy Eric Rein], @shuttlecraft.registered_services.collect{|n,u| n}.sort
|
90
|
+
end
|
91
|
+
|
92
|
+
def make_registrations regs
|
93
|
+
|
94
|
+
@@regs = regs
|
95
|
+
|
96
|
+
class << @shuttlecraft
|
97
|
+
undef_method :read_registered_services
|
98
|
+
end
|
99
|
+
|
100
|
+
def @shuttlecraft.read_registered_services
|
101
|
+
@@regs.collect{|r| [r, DRb.uri]}
|
102
|
+
end
|
103
|
+
|
104
|
+
def @shuttlecraft.registered_services_ary
|
105
|
+
@registered_services_ary
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def test_registration
|
110
|
+
@shuttlecraft.register
|
111
|
+
|
112
|
+
assert_equal true, @shuttlecraft.registered?
|
113
|
+
end
|
114
|
+
|
115
|
+
def test_unregistration
|
116
|
+
@stub_mothership.write([:name, @shuttlecraft.name, DRb.uri])
|
117
|
+
|
118
|
+
assert_equal true, @shuttlecraft.registered?
|
119
|
+
|
120
|
+
@shuttlecraft.unregister
|
121
|
+
|
122
|
+
assert_equal false, @shuttlecraft.registered?
|
123
|
+
end
|
124
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
require 'shuttlecraft/test'
|
2
|
+
|
3
|
+
class TestShuttlecraftMothership < Shuttlecraft::Test
|
4
|
+
|
5
|
+
def setup
|
6
|
+
@mothership = Shuttlecraft::Mothership.new(name: 'Enterprise')
|
7
|
+
end
|
8
|
+
|
9
|
+
def test_initialization
|
10
|
+
assert_equal 'Enterprise', @mothership.name
|
11
|
+
assert_equal :Mothership, @mothership.protocol.service_name
|
12
|
+
assert_equal [], @mothership.registered_services
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_protocol
|
16
|
+
@mothership = Shuttlecraft::Mothership.new(protocol: Shuttlecraft::Protocol.new(:Foo, "Foobar"))
|
17
|
+
|
18
|
+
assert_equal 'Foobar', @mothership.name
|
19
|
+
assert_equal :Foo, @mothership.protocol.service_name
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_each_service_uri
|
23
|
+
@mothership.registered_services << ['name', DRb.uri]
|
24
|
+
|
25
|
+
e = @mothership.each_service_uri
|
26
|
+
|
27
|
+
assert_equal DRb.uri, e.next
|
28
|
+
|
29
|
+
uris = []
|
30
|
+
|
31
|
+
@mothership.each_service_uri do |uri|
|
32
|
+
uris << uri
|
33
|
+
end
|
34
|
+
|
35
|
+
refute_empty uris
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_each_client
|
39
|
+
@mothership.registered_services << ['name', 'druby://localhost:1234']
|
40
|
+
|
41
|
+
@mothership.each_client do |client_obj|
|
42
|
+
assert (DRbObject === client_obj)
|
43
|
+
assert_equal 'druby://localhost:1234', client_obj.__drburi
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def test_uses_default_protocol
|
48
|
+
assert_equal Shuttlecraft::Protocol.default, @mothership.protocol
|
49
|
+
end
|
50
|
+
|
51
|
+
def test_update_eh
|
52
|
+
assert @mothership.update?
|
53
|
+
|
54
|
+
@mothership.update
|
55
|
+
|
56
|
+
refute @mothership.update?
|
57
|
+
end
|
58
|
+
|
59
|
+
def test_update
|
60
|
+
make_registrations(%w[Davy Eric])
|
61
|
+
|
62
|
+
assert @mothership.update
|
63
|
+
refute @mothership.update
|
64
|
+
|
65
|
+
assert_equal %w[Davy Eric], @mothership.registered_services_ary.collect{|n,u| n}.sort
|
66
|
+
end
|
67
|
+
|
68
|
+
def test_update_bang
|
69
|
+
make_registrations(%w[Davy Eric])
|
70
|
+
assert @mothership.update
|
71
|
+
|
72
|
+
assert_equal %w[Davy Eric], @mothership.registered_services.collect{|n,u| n}.sort
|
73
|
+
|
74
|
+
make_registrations(%w[Davy Eric Rein])
|
75
|
+
assert @mothership.update!
|
76
|
+
|
77
|
+
assert_equal %w[Davy Eric Rein], @mothership.registered_services.collect{|n,u| n}.sort
|
78
|
+
end
|
79
|
+
|
80
|
+
def make_registrations regs
|
81
|
+
|
82
|
+
@@regs = regs
|
83
|
+
|
84
|
+
class << @mothership
|
85
|
+
undef_method :read_registered_services
|
86
|
+
end
|
87
|
+
|
88
|
+
def @mothership.read_registered_services
|
89
|
+
@@regs.collect{|r| [r, DRb.uri]}
|
90
|
+
end
|
91
|
+
|
92
|
+
def @mothership.registered_services_ary
|
93
|
+
@registered_services_ary
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'shuttlecraft/test'
|
2
|
+
|
3
|
+
class TestShuttlecraftProtocol < Shuttlecraft::Test
|
4
|
+
|
5
|
+
def setup
|
6
|
+
@protocol = Shuttlecraft::Protocol.new(:MySpecialProtocol, 'Special Snowflake')
|
7
|
+
end
|
8
|
+
|
9
|
+
def test_initialization
|
10
|
+
assert_equal :MySpecialProtocol, @protocol.service_name
|
11
|
+
assert_equal "Special Snowflake", @protocol.name
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_default_protocol
|
15
|
+
assert_equal :Mothership, Shuttlecraft::Protocol.default.service_name
|
16
|
+
assert_equal "Mothership", Shuttlecraft::Protocol.default.name
|
17
|
+
end
|
18
|
+
end
|
metadata
ADDED
@@ -0,0 +1,147 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: shuttlecraft
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 0.0.1
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Davy Stevenson
|
9
|
+
- Eric Hodel
|
10
|
+
autorequire:
|
11
|
+
bindir: bin
|
12
|
+
cert_chain: []
|
13
|
+
date: 2013-11-08 00:00:00.000000000 Z
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: RingyDingy
|
17
|
+
version_requirements: !ruby/object:Gem::Requirement
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '1.6'
|
22
|
+
none: false
|
23
|
+
requirement: !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - ~>
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: '1.6'
|
28
|
+
none: false
|
29
|
+
prerelease: false
|
30
|
+
type: :runtime
|
31
|
+
- !ruby/object:Gem::Dependency
|
32
|
+
name: minitest
|
33
|
+
version_requirements: !ruby/object:Gem::Requirement
|
34
|
+
requirements:
|
35
|
+
- - ~>
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '4.7'
|
38
|
+
none: false
|
39
|
+
requirement: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - ~>
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '4.7'
|
44
|
+
none: false
|
45
|
+
prerelease: false
|
46
|
+
type: :development
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: rdoc
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - ~>
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '4.0'
|
54
|
+
none: false
|
55
|
+
requirement: !ruby/object:Gem::Requirement
|
56
|
+
requirements:
|
57
|
+
- - ~>
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
version: '4.0'
|
60
|
+
none: false
|
61
|
+
prerelease: false
|
62
|
+
type: :development
|
63
|
+
- !ruby/object:Gem::Dependency
|
64
|
+
name: hoe
|
65
|
+
version_requirements: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - ~>
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '3.7'
|
70
|
+
none: false
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ~>
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '3.7'
|
76
|
+
none: false
|
77
|
+
prerelease: false
|
78
|
+
type: :development
|
79
|
+
description: "Shuttlecraft is an easy-to-use wrapper for much of the functionality\
|
80
|
+
\ in Rinda. \n\nCreate a Shuttlecraft::Mothership to manage the RingServer and RingProvider,\
|
81
|
+
\ and then many Shuttlecrafts can easily connect to the Mothership. Registration\
|
82
|
+
\ management is easy and automatic.\n\nEasily broadcast messages to all registered\
|
83
|
+
\ services (ie. Shuttlecrafts) from either the Mothership or a particular Shuttlecraft."
|
84
|
+
email:
|
85
|
+
- davy.stevenson@gmail.com
|
86
|
+
- drbrain@segment7.net
|
87
|
+
executables:
|
88
|
+
- mothership_app
|
89
|
+
- shuttlecraft_app
|
90
|
+
extensions: []
|
91
|
+
extra_rdoc_files:
|
92
|
+
- History.txt
|
93
|
+
- Manifest.txt
|
94
|
+
- README.md
|
95
|
+
files:
|
96
|
+
- .autotest
|
97
|
+
- History.txt
|
98
|
+
- LICENSE
|
99
|
+
- Manifest.txt
|
100
|
+
- README.md
|
101
|
+
- Rakefile
|
102
|
+
- bin/mothership_app
|
103
|
+
- bin/shuttlecraft_app
|
104
|
+
- lib/shuttlecraft.rb
|
105
|
+
- lib/shuttlecraft/mothership.rb
|
106
|
+
- lib/shuttlecraft/mothership_app.rb
|
107
|
+
- lib/shuttlecraft/protocol.rb
|
108
|
+
- lib/shuttlecraft/comms.rb
|
109
|
+
- lib/shuttlecraft/resolv.rb
|
110
|
+
- lib/shuttlecraft/shuttlecraft_app.rb
|
111
|
+
- lib/shuttlecraft/test.rb
|
112
|
+
- ringserver.rb
|
113
|
+
- test/test_shuttlecraft.rb
|
114
|
+
- test/test_shuttlecraft_mothership.rb
|
115
|
+
- test/test_shuttlecraft_protocol.rb
|
116
|
+
- .gemtest
|
117
|
+
homepage: http://github.com/davy/shuttlecraft
|
118
|
+
licenses:
|
119
|
+
- MIT
|
120
|
+
post_install_message:
|
121
|
+
rdoc_options:
|
122
|
+
- --main
|
123
|
+
- README.md
|
124
|
+
require_paths:
|
125
|
+
- lib
|
126
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
127
|
+
requirements:
|
128
|
+
- - '>='
|
129
|
+
- !ruby/object:Gem::Version
|
130
|
+
version: '0'
|
131
|
+
none: false
|
132
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
133
|
+
requirements:
|
134
|
+
- - '>='
|
135
|
+
- !ruby/object:Gem::Version
|
136
|
+
version: '0'
|
137
|
+
none: false
|
138
|
+
requirements: []
|
139
|
+
rubyforge_project: shuttlecraft
|
140
|
+
rubygems_version: 1.8.24
|
141
|
+
signing_key:
|
142
|
+
specification_version: 3
|
143
|
+
summary: Shuttlecraft is an easy-to-use wrapper for much of the functionality in Rinda
|
144
|
+
test_files:
|
145
|
+
- test/test_shuttlecraft.rb
|
146
|
+
- test/test_shuttlecraft_mothership.rb
|
147
|
+
- test/test_shuttlecraft_protocol.rb
|