shuttlecraft 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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