minecraftctl 1.1.0 → 2.0.0

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.
data/Gemfile CHANGED
@@ -6,17 +6,17 @@ source "http://rubygems.org"
6
6
  # Add dependencies to develop your gem here.
7
7
  # Include everything needed to run rake, tests, features, etc.
8
8
  group :development do
9
- gem "rspec", "~> 2.3.0"
10
- gem "bundler", "~> 1.0.0"
11
- gem "jeweler", "~> 1.6.4"
12
- gem "rcov", ">= 0"
13
- gem "reek", "~> 1.2.8"
14
- gem "roodi", "~> 2.1.0"
15
- gem "sinatra", ">= 1.2.6"
16
- gem "main", ">= 4.7.3"
17
- gem "haml", ">= 3.1.3"
18
- gem "httpclient", ">= 2.2.1"
19
- gem "open4", ">= 1.1.0"
20
- gem "mongrel", ">= 1.1.5"
9
+ gem "rspec", "~> 2.3.0"
10
+ gem "bundler", "~> 1.0.0"
11
+ gem "jeweler", "~> 1.6.4"
12
+ gem "rcov", ">= 0"
13
+ gem "reek", "~> 1.2.8"
14
+ gem "roodi", "~> 2.1.0"
15
+ gem "sinatra", ">= 1.2.6"
16
+ gem "main", ">= 4.7.3"
17
+ gem "httpclient", ">= 2.2.1"
18
+ gem "mongrel", ">= 1.1.5"
19
+ gem "mongrel", ">= 1.1.5"
20
+ gem "daemon", "~> 1"
21
21
  end
22
22
 
data/Gemfile.lock CHANGED
@@ -4,13 +4,13 @@ GEM
4
4
  arrayfields (4.7.4)
5
5
  cgi_multipart_eof_fix (2.5.0)
6
6
  chronic (0.6.4)
7
+ daemon (1.0.0)
7
8
  daemons (1.1.4)
8
9
  diff-lcs (1.1.3)
9
10
  fastthread (1.0.1)
10
11
  fattr (2.2.0)
11
12
  gem_plugin (0.2.3)
12
13
  git (1.2.5)
13
- haml (3.1.3)
14
14
  httpclient (2.2.1)
15
15
  jeweler (1.6.4)
16
16
  bundler (~> 1.0)
@@ -27,7 +27,6 @@ GEM
27
27
  daemons (>= 1.0.3)
28
28
  fastthread (>= 1.0.1)
29
29
  gem_plugin (>= 0.2.3)
30
- open4 (1.1.0)
31
30
  rack (1.3.2)
32
31
  rake (0.9.2)
33
32
  rcov (0.9.10)
@@ -61,12 +60,11 @@ PLATFORMS
61
60
 
62
61
  DEPENDENCIES
63
62
  bundler (~> 1.0.0)
64
- haml (>= 3.1.3)
63
+ daemon (~> 1)
65
64
  httpclient (>= 2.2.1)
66
65
  jeweler (~> 1.6.4)
67
66
  main (>= 4.7.3)
68
67
  mongrel (>= 1.1.5)
69
- open4 (>= 1.1.0)
70
68
  rcov
71
69
  reek (~> 1.2.8)
72
70
  roodi (~> 2.1.0)
data/README.rdoc CHANGED
@@ -4,16 +4,53 @@ Allows running Minecraft server in background (daemon) and send commands, stop,
4
4
 
5
5
  == Usage
6
6
 
7
- Lounch Minecraft server with:
7
+ Launch Minecraft server with:
8
8
 
9
9
  $ minecraftctlserver <path to minecraft server install dir>
10
10
 
11
11
  And control it with 'minecraftctl', for help try:
12
12
 
13
- $ minecraftctl serverhelp
13
+ $ minecraftctl /
14
+
15
+ Executing console commands:
16
+
17
+ $ minecraftctl /server console help
18
+ $ minecraftctl /server console say hello world
19
+ $ minecraftctl /server console list
20
+ $ minecraftctl /server console kick kazuya
21
+
22
+ Stopping and starting minecraft server:s
23
+
24
+ $ minecraftctl /server stop
25
+ $ minecraftctl /server start
26
+
27
+ Shutting down minecraftctlserver and minecraft server within:
28
+
29
+ $ minecraftctl / shutdown
30
+
31
+ == API
32
+
33
+ The minecraftctlserver exposes HTTP API on port 25560 (by default) and on localhost (by default).
34
+ GET call will return a value for given URI or available API commands for GET / and /server.
35
+ Use space delimited list of arguments as POST data. First argument will be threated as API command.
36
+
37
+ Some cURL examples:
38
+
39
+ $ curl localhost:25560/ # GET / give list of all API calls
40
+ $ curl localhost:25560/pid_file # get PID file location
41
+ $ curl localhost:25560/ -d shutdown # POST / with shutdown POST data will stop the control server
42
+ $ curl localhost:25560/status # get minecraft server status
43
+ $ curl localhost:25560/server -d 'stop' # stop minecraft server
44
+ $ curl localhost:25560/server -d 'start' # stop minecraft server
45
+ $ curl localhost:25560/server -d 'console list' # list all connected users
14
46
 
15
47
  == Changelog
16
48
 
49
+ === v2.0.0
50
+ * New cleaner API - more REST like
51
+ * New API commands: pid, pid_file, dir, out...
52
+ * Cleanups and fixes
53
+
17
54
  === v1.1.0
18
55
  * Output from server is streamed in real time
19
56
  * Fixed problem with initial pid file creation
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.1.0
1
+ 2.0.0
data/bin/minecraftctl CHANGED
@@ -6,22 +6,22 @@ require 'thread'
6
6
  Thread.abort_on_exception = true
7
7
 
8
8
  Main do
9
- description 'controls Minecraft via minecraftctlserver'
9
+ description 'controls minecraft server via minecraftctlserver'
10
10
 
11
11
  option 'server', 's' do
12
12
  default 'localhost'
13
- description 'minecraftctlserver address'
13
+ description 'minecraft control server address'
14
14
  argument_required
15
15
  end
16
16
 
17
17
  option 'port', 'p' do
18
18
  default 25560
19
- description 'minecraftctlserver port'
19
+ description 'minecraft control server port'
20
20
  argument_required
21
21
  end
22
22
 
23
23
  argument 'command' do
24
- description 'command to send to Minecraft server: try "serverhelp"'
24
+ description 'command to send to control server: try "/" for available commands'
25
25
  end
26
26
 
27
27
  argument 'arguments' do
@@ -42,17 +42,17 @@ Main do
42
42
 
43
43
  args = params['arguments'].values
44
44
 
45
- if ['status', 'log', 'inspect', 'list', 'help'].include? command
46
- c.get_async("http://#{params['server'].value}:#{params['port'].value}/#{command}").pop.content.each do |line|
45
+ if args.empty?
46
+ c.get_async("http://#{params['server'].value}:#{params['port'].value}#{command}").pop.content.each do |line|
47
47
  puts line
48
48
  end
49
49
  else
50
- c.post_async("http://#{params['server'].value}:#{params['port'].value}/#{command}", args.join("\n")).pop.content.each do |line|
50
+ c.post_async("http://#{params['server'].value}:#{params['port'].value}#{command}", args.join("\n")).pop.content.each do |line|
51
51
  puts line
52
52
  end
53
53
  end
54
54
  rescue Errno::ECONNREFUSED => e
55
- puts "Falied to connect to minecraftctlserver; please run minecraftctlserver: #{e}"
55
+ puts "Falied to connect to minecraftctlserver; please run minecraftctlserver first: #{e}"
56
56
  end
57
57
  end
58
58
  end
@@ -4,6 +4,8 @@ require 'main'
4
4
  require 'open3'
5
5
  require 'thread'
6
6
  require 'pathname'
7
+ require 'daemon'
8
+ require 'sinatra/base'
7
9
 
8
10
  $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
9
11
 
@@ -38,34 +40,87 @@ class TextCollector < MessageCollectorProcessed
38
40
  end
39
41
  end
40
42
 
41
- class Daemon
42
- def self.daemonize(pid_file, log_file = nil)
43
- exit if fork
44
- Process.setsid # become session leader
45
- exit if fork # and exits
46
- # now in child
43
+ class APIBuilder
44
+ def initialize(sinatra, description, path = '', &block)
45
+ @sinatra = sinatra
46
+ @description = description
47
47
 
48
- # try to lock before we kill stdin/out
49
- lock(pid_file)
48
+ @dsl = DSL.new(sinatra, path, &block).data
49
+ @path = path
50
+ root = self
50
51
 
51
- if log_file
52
- log = File.open(log_file, 'a')
53
- else
54
- log = '/dev/null'
52
+ @sinatra.get(root_path) do
53
+ root.help.join("\n") + "\n"
54
+ end
55
+
56
+ @dsl.gets.each do |op|
57
+ @sinatra.get(path + '/' + op.name, &op.block)
55
58
  end
56
59
 
57
- # disconnect
58
- STDIN.reopen '/dev/null'
59
- STDOUT.reopen log
60
- STDERR.reopen log
60
+ post_map = {}
61
+
62
+ @dsl.posts.each do |op|
63
+ post_map[op.name] = op
64
+ end
65
+
66
+ @sinatra.post(root_path) do
67
+ cmd, *args = request.body.read.split(' ')
68
+ op = post_map[cmd] or return [400, "Unknown argument: #{cmd} for request: #{request.path_info}\n"]
69
+ op.block.call(args)
70
+ end
61
71
  end
62
72
 
63
- def self.lock(pid_file)
64
- pf = File.open(pid_file, File::RDWR | File::CREAT)
65
- fail "Server already running with pid: #{pf.read}" unless pf.flock(File::LOCK_EX|File::LOCK_NB)
66
- pf.truncate(0)
67
- pf.write(Process.pid.to_s)
68
- pf.flush
73
+ def root_path
74
+ @path.empty? ? '/' : @path
75
+ end
76
+
77
+ def help
78
+ out = []
79
+ out << "#{root_path} - #{@description}"
80
+
81
+ @dsl.posts.each do |op|
82
+ out << "#{root_path} #{op.name} - #{op.description}"
83
+ end
84
+
85
+ @dsl.gets.each do |op|
86
+ out << "#{@path}/#{op.name} - #{op.description}"
87
+ end
88
+
89
+ @dsl.paths.each do |path|
90
+ out += path.help
91
+ end
92
+
93
+ out
94
+ end
95
+
96
+ class DSL
97
+ OP = Struct.new(:name, :description, :block)
98
+ def initialize(sinatra, path, &block)
99
+ @sinatra = sinatra
100
+ @path = path
101
+
102
+ @gets = []
103
+ @posts = []
104
+ @paths = []
105
+
106
+ instance_eval(&block)
107
+ end
108
+
109
+ def data
110
+ Struct.new(:gets, :posts, :paths).new(@gets, @posts, @paths)
111
+ end
112
+
113
+ def get(name, description, &block)
114
+ @gets << OP.new(name, description, block)
115
+ end
116
+
117
+ def post(name, description, &block)
118
+ @posts << OP.new(name, description, block)
119
+ end
120
+
121
+ def path(name, description, &block)
122
+ @paths << APIBuilder.new(@sinatra, description, @path + '/' + name, &block)
123
+ end
69
124
  end
70
125
  end
71
126
 
@@ -84,6 +139,12 @@ Main do
84
139
  argument_required
85
140
  end
86
141
 
142
+ option 'bind', 'b' do
143
+ description 'IP address of interface to listen for connections on; use 0.0.0.0 to bind to all interfaces'
144
+ default '127.0.0.1'
145
+ argument_required
146
+ end
147
+
87
148
  option 'foreground', 'f' do
88
149
  description 'don\'t daemonize'
89
150
  end
@@ -106,8 +167,10 @@ Main do
106
167
  end
107
168
 
108
169
  run do
109
- pid_file = Pathname.new(params['minecraft-dir'].value) + params['pid-file'].value
110
- log_file = Pathname.new(params['minecraft-dir'].value) + params['log-file'].value
170
+ pid_file = Pathname.new(params['pid-file'].value)
171
+ log_file = Pathname.new(params['log-file'].value)
172
+
173
+ Dir.chdir(params['minecraft-dir'].value)
111
174
 
112
175
  if params['foreground'].given?
113
176
  Daemon.lock(pid_file)
@@ -115,128 +178,108 @@ Main do
115
178
  Daemon.daemonize(pid_file, log_file)
116
179
  end
117
180
 
118
- require 'sinatra/base'
119
- require 'haml'
120
-
121
- Dir.chdir(params['minecraft-dir'].value)
122
-
123
181
  minecraft = Minecraft.new(params['command'].value)
124
-
125
182
  minecraft.start
126
183
 
127
- s = Sinatra.new
128
- s.set :port, params['port'].value
129
- s.set :environment, 'production'
130
- s.set :server, ['mongrel']
131
- s.set :lock, true
184
+ sinatra = Sinatra.new
185
+ sinatra.set :port, params['port'].value
186
+ sinatra.set :bind, params['bind'].value
187
+ sinatra.set :environment, 'production'
188
+ sinatra.set :server, ['mongrel']
189
+ sinatra.set :lock, true
132
190
 
133
- s.post '/save-all' do
134
- TextCollector.for(minecraft) do
135
- save_all
136
- end
137
- end
191
+ APIBuilder.new(sinatra, 'minecraft control server API') do
192
+ #get 'inspect', 'internal representation of output data' do
193
+ #minecraft.history.map{|m| m.inspect}.join("\n") + "\n"
194
+ #end
138
195
 
139
- s.post '/start' do
140
- TextCollector.for(minecraft) do
141
- start
196
+ get 'dir', 'minecraft directory' do
197
+ Dir.pwd + "\n"
142
198
  end
143
- end
144
199
 
145
- s.post '/stop' do
146
- TextCollector.for(minecraft) do
147
- stop
200
+ get 'pid', 'minecraft control server PID' do
201
+ Process.pid.to_s + "\n"
148
202
  end
149
- end
150
203
 
151
- s.post '/shutdown' do
152
- TextCollector.for(minecraft) do
153
- stop
154
-
155
- pid = Process.pid
156
- Thread.new{ sleep 1; Process.kill(15, pid)}
204
+ get 'pid_file', 'minecraft control server PID file' do
205
+ pid_file.realpath.to_s + "\n"
157
206
  end
158
- end
159
207
 
160
- s.post %r{/(.+)} do |cmd|
161
- args = request.body.read.split("\n")
162
- TextCollector.for(minecraft) do
163
- send(cmd.tr('-', '_').to_sym, *args)
208
+ get 'log_file', 'minecraft control server log file' do
209
+ log_file.realpath.to_s + "\n"
164
210
  end
165
- end
166
-
167
- s.get '/help' do
168
- start = false
169
- TextCollector.for(minecraft) do
170
- help
171
- log "status show server status"
172
- log "log show recent server messages"
173
- log "inspect inspect recent server messages"
174
- end.process do |msg|
175
- msg = msg.to_s
176
211
 
177
- if msg =~ /Console commands:/
178
- start = true
179
- next
180
- end
181
-
182
- next unless start
183
- msg.sub(/help or \?/, 'serverhelp ').sub(/^( |\t)*/, '')
212
+ get 'out', 'output generated by control and minecraft server' do
213
+ minecraft.history.join("\n") + "\n"
184
214
  end
185
- end
186
215
 
187
- s.get '/list' do
188
- TextCollector.for(minecraft) do
189
- list
190
- end
191
- end
216
+ post 'shutdown', 'shutdown minecraft and control server' do
217
+ TextCollector.for(minecraft) do
218
+ minecraft.stop if minecraft.running?
219
+ log "Shutting down minecraftctlserver"
192
220
 
193
- s.get '/status' do
194
- if minecraft.running?
195
- @pid = minecraft.server_pid
196
- haml :status_running
197
- else
198
- haml :status_stopped
221
+ pid = Process.pid
222
+ Thread.new{Process.kill(15, pid)}
223
+ end
199
224
  end
200
- end
201
-
202
- s.get '/inspect' do
203
- @msg = minecraft.history
204
- haml :messages_inspect
205
- end
206
225
 
207
- s.get '/log' do
208
- @msg = minecraft.history
209
- haml :messages
210
- end
226
+ path 'server', 'minecraft server API' do
227
+ get 'status', 'server status' do
228
+ if minecraft.running?
229
+ "running\n"
230
+ else
231
+ "stopped\n"
232
+ end
233
+ end
211
234
 
212
- s.template :status_running do
213
- '= "Minecraft server is running with pid: #{@pid}"'
214
- end
235
+ get 'pid', 'minecraft server PID' do
236
+ if minecraft.server_pid
237
+ minecraft.server_pid.to_s + "\n"
238
+ else
239
+ "Server not running\n"
240
+ end
241
+ end
215
242
 
216
- s.template :status_stopped do
217
- '= "Minecraft server is stopped"'
218
- end
243
+ post 'start', 'starts minecraft server' do
244
+ TextCollector.for(minecraft) do
245
+ start
246
+ end
247
+ end
219
248
 
220
- s.template :messages do
221
- '= @msg.join("\n")'
222
- end
249
+ post 'stop', 'stops minecraft server' do
250
+ TextCollector.for(minecraft) do
251
+ stop
252
+ end
253
+ end
223
254
 
224
- s.template :messages_inspect do
225
- '= @msg.map{|m| m.inspect}.join("\n")'
255
+ post 'console', 'send console command' do |args|
256
+ next "Console command not specified; try 'console help'\n" if args.empty?
257
+ TextCollector.for(minecraft) do
258
+ begin
259
+ send(args.shift.tr('-', '_').to_sym, *args)
260
+ rescue Minecraft::ServerNotRunningError
261
+ log "Server not running"
262
+ end
263
+ end
264
+ end
265
+ end
226
266
  end
227
267
 
228
- s.not_found do
229
- "Request '#{env['REQUEST_PATH']}' not supported, please see '/serverhelp' for details\n"
268
+ sinatra.not_found do
269
+ "Unknown request: #{env['REQUEST_PATH']}\n"
230
270
  end
231
271
 
232
- s.error do
272
+ sinatra.error do
233
273
  "Error in minecraftctlserver while processing request '#{env['REQUEST_PATH']}': #{env['sinatra.error']}\n"
234
274
  end
235
275
 
236
- s.run!
276
+ sinatra.run!
237
277
 
238
278
  # make sure we stop the server on exit
239
- minecraft.stop if minecraft.running?
279
+ if minecraft.running?
280
+ puts 'stopping minecraft'
281
+ minecraft.stop
282
+ end
240
283
  end
241
284
  end
242
285
 
data/lib/minecraft.rb CHANGED
@@ -96,6 +96,12 @@ class Minecraft
96
96
  end
97
97
  end
98
98
 
99
+ class ServerNotRunningError < RuntimeError
100
+ def initialize
101
+ super 'server not running'
102
+ end
103
+ end
104
+
99
105
  def initialize(cmd)
100
106
  @cmd = cmd
101
107
  @in_queue = Queue.new
@@ -191,6 +197,7 @@ class Minecraft
191
197
 
192
198
  unless running?
193
199
  Process.wait(@server_pid)
200
+ @server_pid = nil
194
201
  raise StartupFailedError, @cmd
195
202
  end
196
203
  rescue Errno::ENOENT
@@ -207,10 +214,11 @@ class Minecraft
207
214
  command('stop') do
208
215
  time_operation("Server stop") do
209
216
  Process.wait(@server_pid)
217
+ @server_pid = nil
210
218
  log "Server stopped"
211
219
  end
212
220
 
213
- wait_msg{|m| m.msg == "Server stopped"}
221
+ wait_msg{|m| m.msg == "Minecraft exits"}
214
222
  end
215
223
  end
216
224
  end
@@ -234,7 +242,7 @@ class Minecraft
234
242
  end
235
243
 
236
244
  def command(cmd)
237
- raise RuntimeError, "server not running" unless running?
245
+ raise ServerNotRunningError unless running?
238
246
  @in_queue << "#{cmd}\n"
239
247
  if block_given?
240
248
  yield
data/minecraftctl.gemspec CHANGED
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = "minecraftctl"
8
- s.version = "1.1.0"
8
+ s.version = "2.0.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Jakub Pastuszek"]
12
- s.date = "2011-09-22"
12
+ s.date = "2011-09-26"
13
13
  s.description = "Allows to send messages, start and stop Minecraft server"
14
14
  s.email = "jpastuszek@gmail.com"
15
15
  s.executables = ["minecraftctl", "minecraftctlserver", "minecraftctl", "minecraftctlserver"]
@@ -56,10 +56,10 @@ Gem::Specification.new do |s|
56
56
  s.add_development_dependency(%q<roodi>, ["~> 2.1.0"])
57
57
  s.add_development_dependency(%q<sinatra>, [">= 1.2.6"])
58
58
  s.add_development_dependency(%q<main>, [">= 4.7.3"])
59
- s.add_development_dependency(%q<haml>, [">= 3.1.3"])
60
59
  s.add_development_dependency(%q<httpclient>, [">= 2.2.1"])
61
- s.add_development_dependency(%q<open4>, [">= 1.1.0"])
62
60
  s.add_development_dependency(%q<mongrel>, [">= 1.1.5"])
61
+ s.add_development_dependency(%q<mongrel>, [">= 1.1.5"])
62
+ s.add_development_dependency(%q<daemon>, ["~> 1"])
63
63
  else
64
64
  s.add_dependency(%q<rspec>, ["~> 2.3.0"])
65
65
  s.add_dependency(%q<bundler>, ["~> 1.0.0"])
@@ -69,10 +69,10 @@ Gem::Specification.new do |s|
69
69
  s.add_dependency(%q<roodi>, ["~> 2.1.0"])
70
70
  s.add_dependency(%q<sinatra>, [">= 1.2.6"])
71
71
  s.add_dependency(%q<main>, [">= 4.7.3"])
72
- s.add_dependency(%q<haml>, [">= 3.1.3"])
73
72
  s.add_dependency(%q<httpclient>, [">= 2.2.1"])
74
- s.add_dependency(%q<open4>, [">= 1.1.0"])
75
73
  s.add_dependency(%q<mongrel>, [">= 1.1.5"])
74
+ s.add_dependency(%q<mongrel>, [">= 1.1.5"])
75
+ s.add_dependency(%q<daemon>, ["~> 1"])
76
76
  end
77
77
  else
78
78
  s.add_dependency(%q<rspec>, ["~> 2.3.0"])
@@ -83,10 +83,10 @@ Gem::Specification.new do |s|
83
83
  s.add_dependency(%q<roodi>, ["~> 2.1.0"])
84
84
  s.add_dependency(%q<sinatra>, [">= 1.2.6"])
85
85
  s.add_dependency(%q<main>, [">= 4.7.3"])
86
- s.add_dependency(%q<haml>, [">= 3.1.3"])
87
86
  s.add_dependency(%q<httpclient>, [">= 2.2.1"])
88
- s.add_dependency(%q<open4>, [">= 1.1.0"])
89
87
  s.add_dependency(%q<mongrel>, [">= 1.1.5"])
88
+ s.add_dependency(%q<mongrel>, [">= 1.1.5"])
89
+ s.add_dependency(%q<daemon>, ["~> 1"])
90
90
  end
91
91
  end
92
92
 
@@ -1,92 +1,146 @@
1
1
  require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
2
  require 'httpclient'
3
- require 'open4'
4
3
  require 'timeout'
4
+ require 'spawn'
5
5
 
6
+ $url = 'http://localhost:25560'
6
7
 
7
- $url = 'http://localhost:25560/'
8
+ def get(uri)
9
+ HTTPClient.new.get_content($url + uri)
10
+ end
8
11
 
9
- def start_stub(wait = true)
10
- pid, stdin, stdout, stderr = Open4::popen4(File.dirname(__FILE__) + '/../bin/minecraftctlserver -c ./minecraft ' + File.dirname(__FILE__) + '/stub_server')
12
+ def get_with_status(uri)
13
+ res = HTTPClient.new.get($url + uri)
14
+ return res.status, res.body
15
+ end
11
16
 
12
- if wait
13
- c = HTTPClient.new
14
- Timeout.timeout(10) do
15
- begin
16
- c.get_content($url + "status")
17
- rescue Errno::ECONNREFUSED
18
- sleep 0.4
19
- retry
20
- end
21
- end
22
- end
17
+ def post(uri, data)
18
+ HTTPClient.new.post_content($url + uri, data)
23
19
  end
24
20
 
25
- def stop_stub
26
- HTTPClient.new.post_content($url + "shutdown", '')
21
+ def post_with_status(uri, data)
22
+ res = HTTPClient.new.post($url + uri, data)
23
+ return res.status, res.body
24
+ end
25
+
26
+ def start_stub
27
+ pid, stdin, stdout, stderr = Spawn::spawn(File.dirname(__FILE__) + '/../bin/minecraftctlserver -c ./minecraft ' + File.dirname(__FILE__) + '/stub_server')
27
28
 
28
29
  Timeout.timeout(10) do
29
30
  begin
30
- loop do
31
- HTTPClient.new.get_content($url + "status")
32
- sleep 0.4
33
- end
31
+ @pid_file = get('/pid_file').strip
34
32
  rescue Errno::ECONNREFUSED
35
- rescue => e
36
- puts "got different error: #{e}"
33
+ sleep 0.1
34
+ retry
37
35
  end
38
36
  end
39
- sleep 0.2
37
+ end
38
+
39
+ def stop_stub
40
+ post('/', 'shutdown')
41
+
42
+ # wait pid lock release
43
+ File.open(@pid_file) do |pf|
44
+ pf.flock(File::LOCK_EX)
45
+ end
40
46
  end
41
47
 
42
48
  describe 'minecraftctlserver' do
43
- describe 'text mode HTTP' do
44
- describe 'server startup' do
45
- before :all do
46
- start_stub(false)
47
- end
49
+ describe 'while server ready it should respond to' do
50
+ before :all do
51
+ start_stub
52
+ end
48
53
 
49
- it 'should start the minecraft server and respond to status command when ready' do
50
- Timeout.timeout(10) do
51
- out = nil
52
- begin
53
- out = HTTPClient.new.get_content($url + "status")
54
- rescue Errno::ECONNREFUSED
55
- sleep 0.4
56
- retry
57
- end
58
-
59
- out.should =~ /Minecraft server is running with pid:/
60
- end
61
- end
54
+ it 'GET / with API command list' do
55
+ get('/').should include 'minecraft control server API'
56
+ end
62
57
 
63
- after :all do
64
- stop_stub
65
- end
58
+ it 'GET /pid_file with absolute PID file path' do
59
+ get('/pid_file').should match(%r{/.*spec/stub_server/minecraftctlserver.pid\n$})
60
+ end
61
+
62
+ it 'GET /log_file with absolute log file path' do
63
+ get('/log_file').should match(%r{/.*spec/stub_server/minecraftctlserver.log\n$})
64
+ end
65
+
66
+ it 'GET /pid with PID number of control server process' do
67
+ get('/pid').to_i.should > 0
68
+ end
69
+
70
+ it 'GET /dir with absolute directory path where minecraft server is running from' do
71
+ get('/dir').should match(%r{/.*spec/stub_server\n$})
72
+ end
73
+
74
+ it 'GET /out with content of minecraft server output' do
75
+ get('/out').should include "Loading properties\n"
76
+ end
77
+
78
+ it 'stop and start with POST /server stop and POST /server start' do
79
+ post('/server', 'stop').should include "Server stopped\n"
80
+ post('/server', 'start').should include "Done (5887241893ns)! For help, type \"help\" or \"?\"\n"
81
+ end
82
+
83
+ it 'POST /server start with server aleady running' do
84
+ post('/server', 'start').should include "Server already running\n"
85
+ end
86
+
87
+ it 'GET /server/status with running' do
88
+ get('/server/status').should == "running\n"
89
+ end
90
+
91
+ it 'GET /server/pid with PID number of minecraft server process' do
92
+ get('/server/pid').to_i.should > 0
93
+ end
94
+
95
+ it 'POST /server/console list with list of connected players' do
96
+ post('/server', 'console list').should == "Connected players: kazuya\n"
97
+ end
98
+
99
+ it 'POST /server/console with error' do
100
+ post('/server', 'console').should == "Console command not specified; try 'console help'\n"
101
+ end
102
+
103
+ it 'POST / blah with error' do
104
+ status, body = post_with_status('/', 'blah')
105
+ status.should == 400
106
+ body.should == "Unknown argument: blah for request: /\n"
66
107
  end
67
108
 
68
- describe 'while server ready' do
109
+ it 'GET /blah with error' do
110
+ status, body = get_with_status('/blah')
111
+ status.should == 404
112
+ body.should == "Unknown request: /blah\n"
113
+ end
114
+
115
+ describe '(having minecraft server stopped)' do
69
116
  before :all do
70
- start_stub
117
+ post('/server', 'stop')
118
+ end
119
+
120
+ it 'POST /server stop with server aleady stopped' do
121
+ post('/server', 'stop').should include "Server already stopped\n"
71
122
  end
72
123
 
73
- it 'should respond to GET /list' do
74
- HTTPClient.new.get_content($url + "list").should == "Connected players: kazuya\n"
124
+ it 'GET /server/status with stopped' do
125
+ get('/server/status').should == "stopped\n"
75
126
  end
76
127
 
77
- it 'should stop and start with POST /stop and POST /start' do
78
- HTTPClient.new.post_content($url + "stop", '').should include "Server stopped\n"
79
- HTTPClient.new.post_content($url + "start", '').should include 'Done (5887241893ns)! For help, type "help" or "?"'
128
+ it 'GET /server/pid with error' do
129
+ get('/server/pid').should == "Server not running\n"
80
130
  end
81
131
 
82
- it 'should respond to GET /help' do
83
- HTTPClient.new.get_content($url + "help").should include "show server status\n"
132
+ it 'POST /server/console list with error' do
133
+ post('/server', 'console list').should == "Server not running\n"
84
134
  end
85
135
 
86
136
  after :all do
87
- stop_stub
137
+ post('/server', 'start')
88
138
  end
89
139
  end
140
+
141
+ after :all do
142
+ stop_stub
143
+ end
90
144
  end
91
145
  end
92
146
 
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: minecraftctl
3
3
  version: !ruby/object:Gem::Version
4
- hash: 19
4
+ hash: 15
5
5
  prerelease:
6
6
  segments:
7
- - 1
8
- - 1
7
+ - 2
9
8
  - 0
10
- version: 1.1.0
9
+ - 0
10
+ version: 2.0.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Jakub Pastuszek
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2011-09-22 00:00:00 Z
18
+ date: 2011-09-26 00:00:00 Z
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
21
21
  requirement: &id001 !ruby/object:Gem::Requirement
@@ -151,12 +151,12 @@ dependencies:
151
151
  - !ruby/object:Gem::Version
152
152
  hash: 5
153
153
  segments:
154
- - 3
154
+ - 2
155
+ - 2
155
156
  - 1
156
- - 3
157
- version: 3.1.3
157
+ version: 2.2.1
158
158
  version_requirements: *id009
159
- name: haml
159
+ name: httpclient
160
160
  prerelease: false
161
161
  type: :development
162
162
  - !ruby/object:Gem::Dependency
@@ -165,14 +165,14 @@ dependencies:
165
165
  requirements:
166
166
  - - ">="
167
167
  - !ruby/object:Gem::Version
168
- hash: 5
168
+ hash: 25
169
169
  segments:
170
- - 2
171
- - 2
172
170
  - 1
173
- version: 2.2.1
171
+ - 1
172
+ - 5
173
+ version: 1.1.5
174
174
  version_requirements: *id010
175
- name: httpclient
175
+ name: mongrel
176
176
  prerelease: false
177
177
  type: :development
178
178
  - !ruby/object:Gem::Dependency
@@ -181,30 +181,28 @@ dependencies:
181
181
  requirements:
182
182
  - - ">="
183
183
  - !ruby/object:Gem::Version
184
- hash: 19
184
+ hash: 25
185
185
  segments:
186
186
  - 1
187
187
  - 1
188
- - 0
189
- version: 1.1.0
188
+ - 5
189
+ version: 1.1.5
190
190
  version_requirements: *id011
191
- name: open4
191
+ name: mongrel
192
192
  prerelease: false
193
193
  type: :development
194
194
  - !ruby/object:Gem::Dependency
195
195
  requirement: &id012 !ruby/object:Gem::Requirement
196
196
  none: false
197
197
  requirements:
198
- - - ">="
198
+ - - ~>
199
199
  - !ruby/object:Gem::Version
200
- hash: 25
200
+ hash: 1
201
201
  segments:
202
202
  - 1
203
- - 1
204
- - 5
205
- version: 1.1.5
203
+ version: "1"
206
204
  version_requirements: *id012
207
- name: mongrel
205
+ name: daemon
208
206
  prerelease: false
209
207
  type: :development
210
208
  description: Allows to send messages, start and stop Minecraft server