vrowser 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.
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