minecraftctl 1.1.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
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