shuttlecraft 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
File without changes
@@ -0,0 +1,6 @@
1
+ === 0.0.1 / 2013-10-07
2
+
3
+ * 1 major enhancement
4
+
5
+ * Birthday!
6
+
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.
@@ -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
@@ -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.
@@ -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
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'shuttlecraft'
4
+
5
+ Shuttlecraft::MothershipApp.run
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'shuttlecraft'
4
+
5
+ Shuttlecraft::ShuttlecraftApp.run
@@ -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
+
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby -w
2
+
3
+ require 'ringy_dingy/ring_server'
4
+
5
+ puts "You should really use ringy_dingy's ring_server"
6
+ puts "Starting RingServer for you anyway!"
7
+ RingyDingy::RingServer.run
8
+
@@ -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