vrowser 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
data/.gitignore ADDED
@@ -0,0 +1,49 @@
1
+ # rcov generated
2
+ coverage
3
+ coverage.data
4
+
5
+ # rdoc generated
6
+ rdoc
7
+
8
+ # yard generated
9
+ doc
10
+ .yardoc
11
+
12
+ # bundler
13
+ .bundle
14
+
15
+ # jeweler generated
16
+ pkg
17
+
18
+ # Have editor/IDE/OS specific files you need to ignore? Consider using a global gitignore:
19
+ #
20
+ # * Create a file at ~/.gitignore
21
+ # * Include files you want ignored
22
+ # * Run: git config --global core.excludesfile ~/.gitignore
23
+ #
24
+ # After doing this, these files will be ignored in all your git projects,
25
+ # saving you from having to 'pollute' every project you touch with them
26
+ #
27
+ # Not sure what to needs to be ignored for particular editors/OSes? Here's some ideas to get you started. (Remember, remove the leading # of the line)
28
+ #
29
+ # For MacOS:
30
+ #
31
+ #.DS_Store
32
+
33
+ # For TextMate
34
+ #*.tmproj
35
+ #tmtags
36
+
37
+ # For emacs:
38
+ #*~
39
+ #\#*
40
+ #.\#*
41
+
42
+ # For vim:
43
+ #*.swp
44
+
45
+ # For redcar:
46
+ #.redcar
47
+
48
+ # For rubinius:
49
+ #*.rbc
data/LICENSE.txt ADDED
@@ -0,0 +1 @@
1
+ Copyright (c) 2012 kimoto
data/README.rdoc ADDED
@@ -0,0 +1,7 @@
1
+ = vrowser
2
+ Server browser for many games (left4Dead2, TeamFortress2, etc)
3
+
4
+ == Copyright
5
+ Copyright (c) 2012 kimoto. See LICENSE.txt for
6
+ further details.
7
+
data/Rakefile ADDED
@@ -0,0 +1,56 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "vrowser"
8
+ gem.summary = %Q{TODO: one-line summary of your gem}
9
+ gem.description = %Q{TODO: longer description of your gem}
10
+ gem.email = "sub+peerler@gmail.com"
11
+ gem.homepage = "http://github.com/kimoto/vrowser"
12
+ gem.authors = ["kimoto"]
13
+ gem.add_development_dependency "thoughtbot-shoulda"
14
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
15
+ end
16
+ rescue LoadError
17
+ puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
18
+ end
19
+
20
+ require 'rake/testtask'
21
+ Rake::TestTask.new(:test) do |test|
22
+ test.libs << 'lib' << 'test'
23
+ test.pattern = 'test/**/*_test.rb'
24
+ test.verbose = true
25
+ end
26
+
27
+ begin
28
+ require 'rcov/rcovtask'
29
+ Rcov::RcovTask.new do |test|
30
+ test.libs << 'test'
31
+ test.pattern = 'test/**/*_test.rb'
32
+ test.verbose = true
33
+ end
34
+ rescue LoadError
35
+ task :rcov do
36
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
37
+ end
38
+ end
39
+
40
+ task :test => :check_dependencies
41
+
42
+ task :default => :test
43
+
44
+ require 'rake/rdoctask'
45
+ Rake::RDocTask.new do |rdoc|
46
+ if File.exist?('VERSION')
47
+ version = File.read('VERSION')
48
+ else
49
+ version = ""
50
+ end
51
+
52
+ rdoc.rdoc_dir = 'rdoc'
53
+ rdoc.title = "vrowser #{version}"
54
+ rdoc.rdoc_files.include('README*')
55
+ rdoc.rdoc_files.include('lib/**/*.rb')
56
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.1
data/bin/vrowser ADDED
@@ -0,0 +1,35 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: utf-8
3
+ # Author: kimoto
4
+ require 'vrowser'
5
+ require 'optparse'
6
+
7
+ options = {}
8
+ parser = OptionParser.new{ |opts|
9
+ opts.banner = "Usage: #{File.basename($0)}"
10
+ opts.on("-f", "--config-file=PATH", "specify config file"){ |v|
11
+ options[:config_path] = v
12
+ }
13
+ }
14
+ parser.parse!
15
+
16
+ if options[:config_path].nil?
17
+ parser.help.display
18
+ exit(1)
19
+ end
20
+
21
+ Vrowser.load_file(options[:config_path]) do |vrowser|
22
+ case sub_command = ARGV.shift
23
+ when "fetch"
24
+ vrowser.fetch
25
+ when "update"
26
+ vrowser.update
27
+ vrowser.clear
28
+ when "list"
29
+ puts vrowser.servers.map(&:name).join($/)
30
+ when "json"
31
+ vrowser.active_servers.select(:name, :host, :ping, :num_players, :type, :map, :players).order(:host).map(&:values).to_json.display
32
+ else
33
+ raise ArgumentError
34
+ end
35
+ end
@@ -0,0 +1,14 @@
1
+ qstat:
2
+ master_server: "hl2master.steampowered.com:27011"
3
+ gametype: stm
4
+ gamename: left4dead2
5
+ maxping: 100
6
+ protocol: a2s
7
+ plugins:
8
+ - l4d2
9
+ database:
10
+ adapter: sqlite
11
+ database: /tmp/server.db
12
+ pool: 5
13
+ timeout: 5000
14
+
@@ -0,0 +1,52 @@
1
+ class Vrowser
2
+ def self.recommend_game_type(info)
3
+ server_name = info.server_name
4
+ game_type = info.suggest_game_type
5
+ if game_type != 'unknown'
6
+ return game_type
7
+ end
8
+
9
+ if server_name =~ /co\-?op/i
10
+ return 'coop'
11
+ elsif server_name =~ /scavenge/i
12
+ return 'scavenge'
13
+ elsif server_name =~ /survival/i
14
+ return 'survival'
15
+ elsif server_name =~ /realism/i
16
+ return 'realism'
17
+ elsif server_name =~ /versus/i
18
+ return 'versus'
19
+ elsif server_name =~ /hard(eight|six|twelve)/i
20
+ return 'coop'
21
+ elsif server_name =~ /(exp(ert)?|advance|easy|normal)/i
22
+ return 'coop'
23
+ elsif server_name =~ /RPG/
24
+ return 'coop'
25
+ elsif server_name =~ /4vs4/i
26
+ return 'versus'
27
+ elsif server_name =~ /vs/i
28
+ return 'versus'
29
+ elsif server_name =~ /team/i
30
+ return 'versus'
31
+ elsif server_name =~ /confogl/i
32
+ return 'versus'
33
+ elsif server_name =~ /fresh(\s*config)?/i
34
+ return 'versus'
35
+ elsif server_name =~ /skullsaba/i
36
+ return 'versus'
37
+ elsif server_name =~ /S A M U R A i/i
38
+ return 'versus'
39
+ elsif info.number_of_max_players == '4'
40
+ return 'coop'
41
+ elsif info.number_of_max_players == '8'
42
+ return 'versus'
43
+ else
44
+ return 'unknown'
45
+ end
46
+ end
47
+
48
+ def self.before_update(server_info)
49
+ server_info.game_type = recommend_game_type(server_info)
50
+ server_info
51
+ end
52
+ end
data/lib/vrowser.rb ADDED
@@ -0,0 +1,309 @@
1
+ # encoding: utf-8
2
+ require 'ruby-qstat'
3
+ require 'sequel'
4
+ require 'logger'
5
+ require 'retry-handler'
6
+ require 'active_support/core_ext'
7
+ require 'yaml'
8
+ require 'json'
9
+
10
+ module VrowserModel
11
+ def self.connect(options={})
12
+ Sequel::Model.plugin(:schema)
13
+ Sequel.connect(options)
14
+ self.define_models
15
+ Servers.plugin :timestamps, :create=>:created_at, :update=>:updated_at
16
+ end
17
+
18
+ def self.define_models
19
+ module_eval %{
20
+ class Servers < Sequel::Model
21
+ unless table_exists?
22
+ set_schema do
23
+ primary_key :id
24
+ string :name
25
+ string :host, :unique => true
26
+ string :status
27
+ integer :ping
28
+ string :num_players
29
+ string :type
30
+ string :map
31
+ string :players
32
+ timestamp :created_at
33
+ timestamp :updated_at
34
+ end
35
+ create_table
36
+ end
37
+ end
38
+ }
39
+ end
40
+ end
41
+
42
+ class Vrowser
43
+ include VrowserModel
44
+
45
+ @@logger = Logger.new(STDOUT)
46
+ def self.logger=(logger)
47
+ @@logger = logger
48
+ end
49
+
50
+ def self.qstat_path=(path)
51
+ QStat.qstat_path = path
52
+ end
53
+
54
+ def self.update_serverlist(host, gametype, gamename, maxping)
55
+ inserted = updated = 0
56
+
57
+ servers = self.fetch_serverlist(host, gametype, gamename, maxping)
58
+ servers.each{ |sv|
59
+ if sv.rules.empty?
60
+ game_type = "unknown"
61
+ else
62
+ game_type = sv.rules.first.game_tags.first
63
+ end
64
+ @@logger.info "game_type is #{game_type}"
65
+
66
+ @@logger.info "finding hostname: #{sv.addr}"
67
+ record = Servers.find(:host => sv.addr)
68
+ @@logger.info "record result: #{record.inspect}"
69
+
70
+ if record.nil?
71
+ @@logger.info "new record for #{sv.addr}"
72
+ Servers.insert(:name => sv.server_name, :host => sv.addr,
73
+ :status => sv.status, :ping => sv.ping, :num_players => sv.number_of_players,
74
+ :type => game_type, :map => sv.map, :players => sv.players.map(&:name).join(',')
75
+ )
76
+ inserted += 1
77
+ else
78
+ @@logger.info "already exist hostname, record update: #{sv.addr}"
79
+ record.update(
80
+ :name => sv.server_name, :status => sv.status,
81
+ :ping => sv.ping,
82
+ #:num_players => sv.number_of_players,
83
+ #:type => game_type,
84
+ #:map => sv.map,
85
+ #:players => sv.players.map(&:name).join(',')
86
+ )
87
+ updated += 1
88
+ end
89
+ }
90
+ @@logger.info "updated exit: inserted:#{inserted}, updated:#{updated}"
91
+ servers
92
+ end
93
+
94
+ def self.update_registered_all(protocol)
95
+ updated = 0
96
+ begin
97
+ Servers.all.each{ |server|
98
+ @@logger.info "trying to update: #{server.host}, #{server.name}"
99
+ self.update(server.host, protocol)
100
+ updated += 1
101
+ }
102
+ rescue
103
+ @@logger.error $!
104
+ ensure
105
+ @@logger.info "updated #{updated} servers"
106
+ end
107
+ end
108
+
109
+ def self.update_info_registered_all(protocol)
110
+ updated = 0
111
+ begin
112
+ Servers.all.each{ |server|
113
+ @@logger.info "trying to update_info: #{server.host}, #{server.name}"
114
+ self.update_info(server.host, protocol)
115
+ updated += 1
116
+ }
117
+ rescue
118
+ @@logger.error $!
119
+ ensure
120
+ @@logger.info "updated #{updated} servers"
121
+ end
122
+ end
123
+
124
+ def self.update(host, protocol)
125
+ new_info = QStat.query(host, protocol)
126
+ if new_info.nil? or new_info.no_response? or new_info.down?
127
+ @@logger.info "server is downing"
128
+ Servers.find(:host => host).update(:status => 'DOWN')
129
+ return
130
+ end
131
+
132
+ new_info = self.before_update(new_info)
133
+ @@logger.info "game_type is #{new_info.game_type}"
134
+
135
+ record = Servers.find(:host => host)
136
+ if record
137
+ record.update(:name => new_info.server_name,
138
+ :status => 'UP', :ping => new_info.ping,
139
+ :num_players => new_info.number_of_players,
140
+ :type => new_info.game_type,
141
+ :map => new_info.map,
142
+ :players => new_info.players.map(&:name).join(','))
143
+ @@logger.info "updated: #{new_info.addr}, #{new_info.server_name}"
144
+ else
145
+ @@logger.info "not found record: #{record.inspect.players}"
146
+ @@logger.info "#{new_info.server_name}"
147
+ Servers.insert(:host => new_info.addr, :name => new_info.server_name,
148
+ :status => 'UP', :ping => new_info.ping, :num_players => new_info.number_of_players,
149
+ :type => new_info.game_type, :map => new_info.map,
150
+ :players => new_info.players.map(&:name).join(','))
151
+ @@logger.info "inserted: #{new_info.addr}, #{new_info.server_name}"
152
+ end
153
+ end
154
+
155
+ def self.before_update(server_info)
156
+ server_info
157
+ end
158
+
159
+ def self.update_info(host, protocol)
160
+ new_info = QStat.query_serverinfo(host, protocol)
161
+ if new_info.nil? or new_info.no_response? or new_info.down?
162
+ @@logger.info "server is downing"
163
+ Servers.find(:host => host).update(:status => 'DOWN')
164
+ return
165
+ end
166
+
167
+ new_info = self.before_update(new_info)
168
+ @@logger.info "game_type is #{new_info.game_type}"
169
+
170
+ record = Servers.find(:host => host)
171
+ if record
172
+ record.update(:name => new_info.server_name,
173
+ :status => 'UP', :ping => new_info.ping,
174
+ :type => new_info.game_type,
175
+ :map => new_info.map)
176
+ @@logger.info "updated: #{new_info.addr}, #{new_info.server_name}"
177
+ else
178
+ @@logger.info "not found record: #{record.inspect.players}"
179
+ @@logger.info "#{new_info.server_name}"
180
+ Servers.insert(:host => new_info.addr, :name => new_info.server_name,
181
+ :status => 'UP', :ping => new_info.ping,
182
+ :type => new_info.game_type, :map => new_info.map)
183
+ @@logger.info "inserted: #{new_info.addr}, #{new_info.server_name}"
184
+ end
185
+ end
186
+
187
+ def self.servers
188
+ Servers.all
189
+ end
190
+
191
+ def self.active_servers
192
+ Servers.filter(:status => 'UP')
193
+ end
194
+
195
+ def self.remove_all
196
+ o = Servers.delete
197
+ @@logger.info "remove all records: #{o}"
198
+ end
199
+
200
+ def self.remove_debris
201
+ records = Servers.filter(:status => 'DOWN')
202
+ @@logger.info "remove debris: count #{records.count}"
203
+ records.delete
204
+ @@logger.info "removed"
205
+ end
206
+
207
+ def self.fetch_serverlist(host, gametype, gamename, maxping)
208
+ proc{
209
+ @@logger.info "try to fetch server list"
210
+ return QStat.query_serverlist(host, gametype, gamename, maxping)
211
+ }.retry(:accept_exception => StandardError, :logger => @@logger)
212
+ rescue => ex
213
+ @@logger.error "error: #{ex}"
214
+ return []
215
+ end
216
+
217
+ def self.read_serverlist_from_xml(path)
218
+ QStat.read_from_xml(path)
219
+ end
220
+
221
+ def self.debug_list
222
+ Servers.all.each{ |server|
223
+ puts "#{server.name}, #{server.host}"
224
+ }
225
+ end
226
+
227
+ def self.update_server_types
228
+ active_servers.each{ |sv|
229
+ update(sv.host)
230
+ }
231
+ end
232
+
233
+ def self.plugin_dir
234
+ File.expand_path(File.join(File.dirname(__FILE__), "./plugins"))
235
+ end
236
+
237
+ def self.plugin_path(plugin_name)
238
+ File.expand_path(File.join(self.plugin_dir, plugin_name + ".rb"))
239
+ end
240
+
241
+ def self.load_all_plugins
242
+ self.load_plugins(self.plugin_dir)
243
+ end
244
+
245
+ def self.load_plugins(dir)
246
+ Dir.entries(dir).each{ |entry|
247
+ next if entry == "." or entry == ".."
248
+ load File.join(dir, entry)
249
+ }
250
+ end
251
+
252
+ def self.load_plugin(plugin_name)
253
+ load self.plugin_path(plugin_name.to_s)
254
+ end
255
+
256
+ def self.load_config(config)
257
+ raise ArgumentError.new("config['plugins']") unless config["plugins"]
258
+ raise ArgumentError.new("config['qstat']") unless config["qstat"]
259
+ raise ArgumentError.new("config['database']") unless config["database"]
260
+
261
+ VrowserModel.connect(config["database"])
262
+
263
+ config['plugins'].each{ |plugin_symbol|
264
+ Vrowser.load_plugin plugin_symbol
265
+ }
266
+
267
+ Vrowser.new(config['qstat'].symbolize_keys)
268
+ end
269
+
270
+ def self.load_file(path)
271
+ instance = self.load_config YAML.load_file(path)
272
+ yield(instance) if block_given?
273
+ instance
274
+ end
275
+
276
+ #### ==== instance methods
277
+ def initialize(options={})
278
+ @master_server = options[:master_server] or raise ArgumentError("master_server")
279
+ @gametype = options[:gametype] or raise ArgumentError("gametype")
280
+ @gamename = options[:gamename] or raise ArgumentError("gamename")
281
+ @protocol = options[:protocol] or raise ArgumentError("protocol")
282
+ @maxping = options[:maxping] ||= 130
283
+ yield(self) if block_given?
284
+ end
285
+
286
+ def fetch
287
+ self.class.update_serverlist(@master_server, @gametype, @gamename, @maxping)
288
+ end
289
+
290
+ def update
291
+ self.class.update_registered_all(@protocol)
292
+ end
293
+
294
+ def update_only_info
295
+ self.class.update_info_registered_all(@protocol)
296
+ end
297
+
298
+ def clear
299
+ self.class.remove_debris
300
+ end
301
+
302
+ def servers
303
+ self.class.servers
304
+ end
305
+
306
+ def active_servers
307
+ self.class.active_servers
308
+ end
309
+ end
data/test/helper.rb ADDED
@@ -0,0 +1,18 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ begin
4
+ Bundler.setup(:default, :development)
5
+ rescue Bundler::BundlerError => e
6
+ $stderr.puts e.message
7
+ $stderr.puts "Run `bundle install` to install missing gems"
8
+ exit e.status_code
9
+ end
10
+ require 'test/unit'
11
+ require 'shoulda'
12
+
13
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
14
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
15
+ require 'vrowser'
16
+
17
+ class Test::Unit::TestCase
18
+ end
@@ -0,0 +1,7 @@
1
+ require 'helper'
2
+
3
+ class TestVrowser < Test::Unit::TestCase
4
+ should "do not anything" do
5
+ true
6
+ end
7
+ end
metadata ADDED
@@ -0,0 +1,89 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: vrowser
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 0
8
+ - 1
9
+ version: 0.0.1
10
+ platform: ruby
11
+ authors:
12
+ - kimoto
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2012-02-06 00:00:00 +09:00
18
+ default_executable: vrowser
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: thoughtbot-shoulda
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ segments:
29
+ - 0
30
+ version: "0"
31
+ type: :development
32
+ version_requirements: *id001
33
+ description: Server browser for many games (Left4Dead2, TeamFortress2, etc)
34
+ email: sub+peerler@gmail.com
35
+ executables:
36
+ - vrowser
37
+ extensions: []
38
+
39
+ extra_rdoc_files:
40
+ - LICENSE.txt
41
+ - README.rdoc
42
+ files:
43
+ - .document
44
+ - .gitignore
45
+ - LICENSE.txt
46
+ - README.rdoc
47
+ - Rakefile
48
+ - VERSION
49
+ - bin/vrowser
50
+ - examples/config.yml
51
+ - lib/plugins/l4d2.rb
52
+ - lib/vrowser.rb
53
+ - test/helper.rb
54
+ - test/test_vrowser.rb
55
+ has_rdoc: true
56
+ homepage: http://github.com/kimoto/vrowser
57
+ licenses: []
58
+
59
+ post_install_message:
60
+ rdoc_options:
61
+ - --charset=UTF-8
62
+ require_paths:
63
+ - lib
64
+ required_ruby_version: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ segments:
70
+ - 0
71
+ version: "0"
72
+ required_rubygems_version: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ">="
76
+ - !ruby/object:Gem::Version
77
+ segments:
78
+ - 0
79
+ version: "0"
80
+ requirements: []
81
+
82
+ rubyforge_project:
83
+ rubygems_version: 1.3.7
84
+ signing_key:
85
+ specification_version: 3
86
+ summary: Server browser for many games
87
+ test_files:
88
+ - test/helper.rb
89
+ - test/test_vrowser.rb