flox 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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 219bdba8d257f7a35c1b9174877fc74775ce0a89
4
+ data.tar.gz: cc1abbb765aa82c4b6c3d650e308869b9594361e
5
+ SHA512:
6
+ metadata.gz: 77c3fe30874b4bd0531108be4965dfa5178813712f319346cd167fa9b41985737904e095375b7b64792a0135275d1231d1817ba16e023d20b9c4906a5c4d4c86
7
+ data.tar.gz: 01509872cb9f839e79bd2941894d01eee92f5b238404e75aff60f1dba2a4099a0e05e96936840e617a070172ea5e2f0adc993986c95bb03540af14426bc3c9cf
data/bin/flox ADDED
@@ -0,0 +1,102 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ ## Author: Daniel Sperl
4
+ ## Copyright: Copyright 2014 Gamua
5
+ ## License: Simplified BSD
6
+
7
+ $LOAD_PATH << '../lib'
8
+
9
+ require 'flox'
10
+ require 'fileutils'
11
+ require 'trollop'
12
+
13
+ class Worker
14
+
15
+ attr_reader :flox
16
+
17
+ def initialize(game_id, game_key, base_url)
18
+ @flox = Flox.new(game_id, game_key, base_url)
19
+ end
20
+
21
+ def login_with_key(key)
22
+ flox.login_with_key(key)
23
+ end
24
+
25
+ def execute(command_name, args)
26
+ begin
27
+ method_name = command_name.downcase.gsub("-", "_")
28
+ self.send(method_name, **args)
29
+ rescue Exception => e
30
+ log "Error: " + e.to_s
31
+ end
32
+ end
33
+
34
+ private
35
+
36
+ def download_logs(args={})
37
+ query = args[:query]
38
+ limit = args[:limit]
39
+ destination = args[:destination] || Dir.pwd
40
+ FileUtils.mkdir_p(destination)
41
+
42
+ log "Fetching Logs ..."
43
+ log_ids = flox.load_log_ids(query, limit)
44
+ log_ids.each do |log_id|
45
+ remote_path = "logs/#{log_id}"
46
+ local_path = File.join(destination, log_id) + ".json"
47
+ load_and_save_resource(remote_path, local_path)
48
+ end
49
+ end
50
+
51
+ def status(args={})
52
+ log "Fetching Server Status ..."
53
+ status = flox.status
54
+ log "Version: #{status['version']}, status: #{status['status']}"
55
+ end
56
+
57
+ def log(message)
58
+ puts message
59
+ end
60
+
61
+ def fail(message)
62
+ log message
63
+ exit
64
+ end
65
+
66
+ def load_and_save_resource(remote_path, local_path)
67
+ if (File.exists? local_path)
68
+ log("Skipped #{local_path} (file exists)")
69
+ else
70
+ resource = flox.load_resource(remote_path)
71
+ File.write(local_path, JSON.pretty_generate(resource))
72
+ log("Saved #{local_path}")
73
+ end
74
+ end
75
+
76
+ end
77
+
78
+ options = Trollop::options do
79
+ banner "Administrative utility for the Flox.cc Game Backend"
80
+ opt :key, "The 'Hero' key used for authentication", :type => :string, :required => true
81
+ opt :game_id, "The ID of the game", :type => :string, :required => true
82
+ opt :game_key, "The key of the game", :type => :string, :required => true
83
+ opt :base_url, "The URL of the Flox service", :type => :string
84
+ opt :destination, "The directory in which to store the logs", :type => :string
85
+ opt :query, "Narrows down the list of results", :type => :string
86
+ opt :limit, "Maximum number of logs to download", :type => :int
87
+ end
88
+
89
+ commands = ARGV.clone
90
+ # puts "Optons: #{options}"
91
+
92
+ hero_key = options[:key]
93
+ game_id = options[:game_id]
94
+ game_key = options[:game_key]
95
+ base_url = options[:base_url] || Flox::DEFAULT_URL
96
+
97
+ worker = Worker.new(game_id, game_key, base_url)
98
+ worker.login_with_key(hero_key)
99
+
100
+ commands.each do |command|
101
+ worker.execute(command, options)
102
+ end
data/lib/flox.rb ADDED
@@ -0,0 +1,203 @@
1
+ ## Author: Daniel Sperl
2
+ ## Copyright: Copyright 2014 Gamua
3
+ ## License: Simplified BSD
4
+
5
+ # The main class used to interact with the Flox cloud service. Create an
6
+ # instance of Flox using the game ID and key acquired from the web interface,
7
+ # then login with a "Hero" key. That way, you will be able to access the data
8
+ # of all players.
9
+ class Flox
10
+
11
+ # The URL where the Flox servers are found.
12
+ DEFAULT_URL = "https://www.flox.cc"
13
+
14
+ # @private
15
+ attr_reader :service
16
+
17
+ # The player that is currently logged in.
18
+ attr_reader :current_player
19
+
20
+ # Creates a new instance with a certain game ID and key. Per default, a guest
21
+ # player will be logged in. You probably need to call 'login_with_key' with a
22
+ # Hero-key to access your data.
23
+ def initialize(game_id, game_key, base_url=Flox::DEFAULT_URL)
24
+ @service = RestService.new(game_id, game_key, base_url)
25
+ self.login_guest
26
+ end
27
+
28
+ # Makes a key-login on the server. It is recommended to create a 'hero'
29
+ # player in the web interface and use that for the login.
30
+ def login_with_key(key)
31
+ login(:key, key)
32
+ end
33
+
34
+ # Creates a new guest player and logs it in.
35
+ def login_guest()
36
+ login(:guest)
37
+ end
38
+
39
+ # Logging out the current player automatically logs in a new guest.
40
+ alias_method :logout, :login_guest
41
+
42
+ # @private
43
+ def login(auth_type, auth_id=nil, auth_token=nil)
44
+ data = service.login(auth_type, auth_id, auth_token)
45
+ @current_player = Player.new(data['id'], data['entity'])
46
+ end
47
+
48
+ # Loads an entity with a certain type and id from the server.
49
+ # Normally, the type is the class name you used for the entity in your game.
50
+ # Returns a Flox::Entity instance.
51
+ def load_entity(type, id)
52
+ path = entity_path(type, id)
53
+ data = service.get(path)
54
+ if (type == '.player')
55
+ Player.new(id, data)
56
+ else
57
+ Entity.new(type, id, data)
58
+ end
59
+ end
60
+
61
+ # Stores an entity on the server.
62
+ def save_entity(entity)
63
+ result = service.put(entity.path, entity)
64
+ entity['updatedAt'] = result['updatedAt']
65
+ entity['createdAt'] = result['createdAt']
66
+ entity
67
+ end
68
+
69
+ # :call-seq:
70
+ # delete_entity(entity)
71
+ # delete_entity(type, id)
72
+ #
73
+ # Deletes the given entity from the database.
74
+ def delete_entity(*entity)
75
+ if entity.length > 1
76
+ type, id = entity[0], entity[1]
77
+ else
78
+ type, id = entity[0].type, entity[0].id
79
+ end
80
+ service.delete(entity_path(type, id))
81
+ nil
82
+ end
83
+
84
+ # Posts a score to a certain leaderboard. Beware that only the top score of
85
+ # a player will appear on the leaderboard.
86
+ def post_score(leaderboard_id, score, player_name)
87
+ path = leaderboard_path(leaderboard_id)
88
+ data = { playerName: player_name, value: score }
89
+ service.post(path, data)
90
+ end
91
+
92
+ # Loads all scores of a leaderboard, sorted by rank. 'scope' can either be
93
+ # one of the symbols +:today, :this_week, :all_time+ or an array of player IDs.
94
+ def load_scores(leaderboard_id, scope)
95
+ path = leaderboard_path(leaderboard_id)
96
+ args = {}
97
+
98
+ if scope.is_a?(Array)
99
+ args['p'] = scope
100
+ else
101
+ args['t'] = scope.to_s.to_camelcase
102
+ end
103
+
104
+ raw_scores = service.get(path, args)
105
+ raw_scores.collect { |raw_score| Score.new(raw_score) }
106
+ end
107
+
108
+ # Loads a JSON object from the given path. This works with any resource
109
+ # e.g. entities, logs, etc. Always returns a Hash.
110
+ def load_resource(path, args=nil)
111
+ service.get(path, args)
112
+ end
113
+
114
+ # Loads a log with a certain ID. A log is a Hash instance.
115
+ def load_log(log_id)
116
+ log = service.get log_path(log_id)
117
+ log['id'] = log_id unless log['id']
118
+ log
119
+ end
120
+
121
+ # Loads logs, optionally restricted by a certain query.
122
+ # Here are some sample queries:
123
+ #
124
+ # * 'day:2014-02-20' -> all logs of a certain day
125
+ # * 'severity:warning' -> all logs of type warning & error
126
+ # * 'severity:error' -> all logs of type error
127
+ # * 'day:2014-02-20 severity:error' all error logs from February 20th.
128
+ #
129
+ # Returns a Flox::ResourceEnumerator you can use to iterate
130
+ # over the logs.
131
+ def load_logs(query=nil, limit=nil)
132
+ log_ids = load_log_ids(query, limit)
133
+ paths = log_ids.map { |log_id| log_path(log_id) }
134
+ ResourceEnumerator.new(service, paths)
135
+ end
136
+
137
+ # Loads just the IDs of the logs, restricted by a certain query.
138
+ def load_log_ids(query=nil, limit=nil)
139
+ log_ids = []
140
+ cursor = nil
141
+ begin
142
+ args = {}
143
+ args['q'] = query if query
144
+ args['l'] = limit if limit
145
+ args['c'] = cursor if cursor
146
+
147
+ result = service.get "logs", args
148
+ cursor = result["cursor"]
149
+ log_ids += result["ids"]
150
+ limit -= log_ids.length if limit
151
+ end while !cursor.nil? and (limit.nil? or limit > 0)
152
+ log_ids
153
+ end
154
+
155
+ # Loads the status of the Flox service, which is a Hash with the
156
+ # keys 'status' and 'version'.
157
+ def status
158
+ service.get("")
159
+ end
160
+
161
+ # The ID of the game you are accessing.
162
+ def game_id
163
+ service.game_id
164
+ end
165
+
166
+ # The key of the game you are accessing.
167
+ def game_key
168
+ service.game_key
169
+ end
170
+
171
+ # The base URL of the Flox service.
172
+ def base_url
173
+ service.base_url
174
+ end
175
+
176
+ # @return [String] a string representation of the object.
177
+ def inspect
178
+ "[Flox game_id: '#{game_id}', base_url: '#{base_url}']"
179
+ end
180
+
181
+ private
182
+
183
+ def entity_path(type, id)
184
+ "entities/#{type}/#{id}"
185
+ end
186
+
187
+ def leaderboard_path(leaderboard_id)
188
+ "leaderboards/#{leaderboard_id}"
189
+ end
190
+
191
+ def log_path(log_id)
192
+ "logs/#{log_id}"
193
+ end
194
+
195
+ end
196
+
197
+ require 'flox/version'
198
+ require 'flox/utils'
199
+ require 'flox/rest_service'
200
+ require 'flox/entity'
201
+ require 'flox/player'
202
+ require 'flox/score'
203
+ require 'flox/resource_enumerator'
@@ -0,0 +1,103 @@
1
+ ## Author: Daniel Sperl
2
+ ## Copyright: Copyright 2014 Gamua
3
+ ## License: Simplified BSD
4
+
5
+ require 'time'
6
+
7
+ # The base class of all objects that can be stored persistently on the Flox
8
+ # server.
9
+ #
10
+ # The class extends `Hash`. Thus, all properties of the Entity can be accessed
11
+ # as keys of the Entity instance.
12
+ #
13
+ # entity['name'] = 'Donald Duck'
14
+ #
15
+ # For convenience, the standard entity properties (e.g. `created_at` and
16
+ # `updated_at`)can be accessed via Ruby attributes.
17
+ #
18
+ # entity.public_access = 'rw'
19
+ #
20
+ # To load and save an entity, use the respective methods on the Flox class.
21
+ #
22
+ # my_entity = flox.load_entity('SaveGame', '12345') # => Flox::Entity
23
+ # flox.save_entity(my_entity)
24
+ #
25
+ class Flox::Entity < Hash
26
+
27
+ # @return [String] the primary identifier of the entity.
28
+ attr_accessor :id
29
+
30
+ # @return [String] the type of the entity. Types group entities together on the server.
31
+ attr_reader :type
32
+
33
+ # @param type [String] Typically the class name of the entity (as used in the other SDKs).
34
+ # @param id [String] The unique identifier of the entity.
35
+ # @param data [Hash] The initial contents of the entity.
36
+ def initialize(type, id=nil, data=nil)
37
+ @type = type
38
+ @id = id ? id : String.random_uid
39
+ self['createdAt'] = self['updatedAt'] = Time.now.utc.to_xs_datetime
40
+ self.public_access = ''
41
+ self.merge!(data) if data
42
+ end
43
+
44
+ def created_at
45
+ Time.parse self['createdAt']
46
+ end
47
+
48
+ def updated_at
49
+ Time.parse self['updatedAt']
50
+ end
51
+
52
+ def public_access
53
+ self["publicAccess"]
54
+ end
55
+
56
+ def public_access=(access)
57
+ self["publicAccess"] = access.to_s
58
+ end
59
+
60
+ def owner_id
61
+ self["ownerId"]
62
+ end
63
+
64
+ def owner_id=(value)
65
+ self["ownerId"] = value.to_s
66
+ end
67
+
68
+ def path
69
+ "entities/#{@type}/#{@id}"
70
+ end
71
+
72
+ # @return [String] provides a simple string representation of the Entity.
73
+ def inspect
74
+ description = "[#{self.class} #{@id} (#{@type})\n"
75
+ each_pair do |key, value|
76
+ description += " #{key}: #{value}\n"
77
+ end
78
+ description += "]"
79
+ end
80
+
81
+ #
82
+ # documentation hints
83
+ #
84
+
85
+ # @!attribute owner_id
86
+ # @return [String] the player ID of the owner of the entity
87
+ # (referencing a Player entitity).
88
+
89
+ # @!attribute created_at
90
+ # @return [Time] the time the entity was created.
91
+
92
+ # @!attribute updated_at
93
+ # @return [Time] the time the entity was last changed on the server.
94
+
95
+ # @!attribute public_access
96
+ # @return [String] the access rights of all players except the owner
97
+ # (the owner always has unlimited access). Possible values: '', 'r', 'rw'
98
+
99
+ # @!attribute [r] path
100
+ # @return [String] the path to the REST-resource of the entity, relative
101
+ # to the game's root.
102
+
103
+ end
@@ -0,0 +1,43 @@
1
+ ## Author: Daniel Sperl
2
+ ## Copyright: Copyright 2014 Gamua
3
+ ## License: Simplified BSD
4
+
5
+ # An Entity that contains information about a Flox Player.
6
+ # Normally, you don't create instances of `Player` yourself. There's always
7
+ # a player logged in you can access with
8
+ #
9
+ # flox.current_player # => Flox::Player
10
+ #
11
+ # To log in as a different player, you'll probably want to use a `key`-login.
12
+ # When you use the Flox Gem as a maintenance tool, create a "Hero" in the
13
+ # online interface and use its key to login. That way, you have access to
14
+ # all entities, regardless of their `public_access` values.
15
+ #
16
+ # flox.login_with_key 'hero-key' # => Flox::Player
17
+ #
18
+ # The Player class itself is just an entity that adds an `auth_type`
19
+ # property for your convenience.
20
+ class Flox::Player < Flox::Entity
21
+
22
+ # Creates a player with a certain ID and data. The `type` of a player
23
+ # is always `.player` in Flox.
24
+ def initialize(id=nil, data=nil)
25
+ data ||= {}
26
+ data["authType"] ||= "guest"
27
+ data["publicAccess"] ||= "r"
28
+ super(".player", id, data)
29
+ self.owner_id ||= self.id
30
+ end
31
+
32
+ def auth_type
33
+ self["authType"].to_sym
34
+ end
35
+
36
+ #
37
+ # documentation hints
38
+ #
39
+
40
+ # @!attribute auth_type
41
+ # @return [String] the type of authentication the player used to log in.
42
+
43
+ end
@@ -0,0 +1,43 @@
1
+ ## Author: Daniel Sperl
2
+ ## Copyright: Copyright 2014 Gamua
3
+ ## License: Simplified BSD
4
+
5
+ require 'json'
6
+
7
+ # A helper class that stores the paths to a number of REST resources and
8
+ # supports iterating over those resources, downloading them lazily from the
9
+ # server.
10
+ class Flox::ResourceEnumerator
11
+
12
+ include Enumerable
13
+
14
+ # @param rest_service [RestService]
15
+ # the service instance used to download the resources.
16
+ # @param paths [Array<String>]
17
+ # the URLs to the resources that need to be accessed, relative to
18
+ # the game's root.
19
+ def initialize(rest_service, paths)
20
+ @service = rest_service
21
+ @paths = paths
22
+ end
23
+
24
+ # Iterates over the resources provided on intialization, loading them
25
+ # from the server one by one.
26
+ def each
27
+ @paths.each do |path|
28
+ yield @service.get(path)
29
+ end
30
+ end
31
+
32
+ def length
33
+ @paths.length
34
+ end
35
+
36
+ #
37
+ # documentation hints
38
+ #
39
+
40
+ # @!attribute length
41
+ # @return [Fixnum] the total number of objects being enumerated.
42
+
43
+ end
@@ -0,0 +1,128 @@
1
+ ## Author: Daniel Sperl
2
+ ## Copyright: Copyright 2014 Gamua
3
+ ## License: Simplified BSD
4
+
5
+ require 'json'
6
+ require 'yaml'
7
+ require 'net/http'
8
+
9
+ # A class that makes it easy to communicate with the Flox server via a REST protocol.
10
+ class Flox::RestService
11
+
12
+ # @return [String] the unique identifier of the game.
13
+ attr_reader :game_id
14
+
15
+ # @return [String] the key that identifies the game.
16
+ attr_reader :game_key
17
+
18
+ # @return [String] the URL pointing to the Flox REST API.
19
+ attr_reader :base_url
20
+
21
+ def initialize(game_id, game_key, base_url)
22
+ @game_id = game_id
23
+ @game_key = game_key
24
+ @base_url = base_url
25
+ @authentication = { "authType" => "guest" }
26
+ end
27
+
28
+ # Makes a `GET` request at the server. The given data-Hash is URI-encoded
29
+ # and added to the path.
30
+ # @return the server response.
31
+ def get(path, data=nil)
32
+ path = full_path(path)
33
+ path += "?" + URI.encode_www_form(data) if data
34
+ request = Net::HTTP::Get.new(path)
35
+ execute(request)
36
+ end
37
+
38
+ # Makes a `DELETE` request at the server.
39
+ # @return the server response.
40
+ def delete(path)
41
+ request = Net::HTTP::Delete.new(full_path(path))
42
+ execute(request)
43
+ end
44
+
45
+ # Makes a `POST` request at the server. The given data-Hash is transferred
46
+ # in the body of the request.
47
+ # @return the server response.
48
+ def post(path, data=nil)
49
+ request = Net::HTTP::Post.new(full_path(path))
50
+ execute(request, data)
51
+ end
52
+
53
+ # Makes a `PUT` request at the server. The given data-Hash is transferred
54
+ # in the body of the request.
55
+ # @return the server response.
56
+ def put(path, data=nil)
57
+ request = Net::HTTP::Put.new(full_path(path))
58
+ execute(request, data)
59
+ end
60
+
61
+ # Makes a login on the server with the given authentication data.
62
+ # @return the server response.
63
+ def login(auth_type, auth_id=nil, auth_token=nil)
64
+ auth_data = {
65
+ "authType" => auth_type,
66
+ "authId" => auth_id,
67
+ "authToken" => auth_token,
68
+ "id" => auth_id
69
+ }
70
+
71
+ if (auth_type.to_sym == :guest)
72
+ response = auth_data
73
+ else
74
+ response = post("authenticate", auth_data)
75
+ auth_data["id"] = response["id"]
76
+ end
77
+
78
+ @authentication = auth_data
79
+ response
80
+ end
81
+
82
+ # @return [String] provides a simple string representation of the service.
83
+ def inspect
84
+ "[RestService game_id: #{game_id}, base_url: #{base_url}]"
85
+ end
86
+
87
+ private
88
+
89
+ def execute(request, data=nil)
90
+ flox_header = {
91
+ "sdk" => { "type" => "ruby", "version" => Flox::VERSION },
92
+ "gameKey" => @game_key,
93
+ "dispatchTime" => Time.now.utc.to_xs_datetime,
94
+ "player" => @authentication
95
+ }
96
+
97
+ request["Content-Type"] = "application/json"
98
+ request["X-Flox"] = flox_header.to_json
99
+ request.body = data.to_json if data
100
+
101
+ uri = URI.parse(@base_url)
102
+ http = Net::HTTP::new(uri.host, uri.port)
103
+
104
+ if uri.scheme == "https" # enable SSL/TLS
105
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
106
+ http.use_ssl = true
107
+ end
108
+
109
+ http.start do |session|
110
+ response = session.request(request)
111
+ if (response.is_a? Net::HTTPSuccess)
112
+ return JSON.parse(response.body || '{}')
113
+ else
114
+ message = begin
115
+ JSON.parse(response.body)['message']
116
+ rescue
117
+ response.body
118
+ end
119
+ raise message
120
+ end
121
+ end
122
+ end
123
+
124
+ def full_path(path)
125
+ "/api/games/#{@game_id}/#{path}"
126
+ end
127
+
128
+ end
data/lib/flox/score.rb ADDED
@@ -0,0 +1,34 @@
1
+ ## Author: Daniel Sperl
2
+ ## Copyright: Copyright 2014 Gamua
3
+ ## License: Simplified BSD
4
+
5
+ # Provides information about the value and origin of one posted score entry.
6
+ class Flox::Score
7
+
8
+ # @return [String] the ID of the player who posted the score.
9
+ # Note that this could be a guest player unknown to the server.
10
+ attr_reader :player_id
11
+
12
+ # @return [String] the name of the player who posted the score.
13
+ attr_reader :player_name
14
+
15
+ # @return [Fixnum] the actual score.
16
+ attr_reader :value
17
+
18
+ # @return [String] the country from which the score originated, in a
19
+ # two-letter country code.
20
+ attr_reader :country
21
+
22
+ # @return [Time] the date at which the score was posted.
23
+ attr_reader :created_at
24
+
25
+ # @param data [Hash] the contents of the score as given by the Flox server.
26
+ def initialize(data)
27
+ @player_id = data['playerId'].to_s
28
+ @player_name = data['playerName'].to_s
29
+ @value = data['value'].to_i
30
+ @country = data['country'].to_s
31
+ @created_at = Time.parse(data['createdAt'].to_s)
32
+ end
33
+
34
+ end
data/lib/flox/utils.rb ADDED
@@ -0,0 +1,38 @@
1
+ ## Author: Daniel Sperl
2
+ ## Copyright: Copyright 2014 Gamua
3
+ ## License: Simplified BSD
4
+
5
+ require 'securerandom'
6
+
7
+ # Flox-extensions to the standard Time class.
8
+ class Time
9
+
10
+ # @return [String] an XS-DateTime representation of the string, like this:
11
+ # `2014-02-20T20:15:00.123Z`
12
+ def to_xs_datetime
13
+ strftime("%Y-%m-%dT%H:%M:%S.%LZ")
14
+ end
15
+
16
+ end
17
+
18
+ # Flox-extensions to the standard Time class.
19
+ class String
20
+
21
+ # @return [String] creates a random alphanumeric string with a given length.
22
+ def self.random_uid(length=16)
23
+ SecureRandom.base64(length * 2).gsub(/[\+\/]/, '').slice(0, length)
24
+ end
25
+
26
+ # @return [String] converts a `camelCase` string to its `under_score` equivalent.
27
+ def to_underscore
28
+ gsub(/(.)([A-Z])/,'\1_\2').downcase
29
+ end
30
+
31
+ # @return [String] converts a string that separates its words with space,
32
+ # underscore or dash into its `camelCase` equivalent.
33
+ def to_camelcase
34
+ words = downcase.split(/[_\-\s]/)
35
+ words.shift + words.map(&:capitalize).join
36
+ end
37
+
38
+ end
@@ -0,0 +1,10 @@
1
+ ## Author: Daniel Sperl
2
+ ## Copyright: Copyright 2014 Gamua
3
+ ## License: Simplified BSD
4
+
5
+ class Flox
6
+
7
+ # The current version of the Flox SDK.
8
+ VERSION = "0.0.1"
9
+
10
+ end
@@ -0,0 +1,52 @@
1
+ ## Author: Daniel Sperl
2
+ ## Copyright: Copyright 2014 Gamua
3
+ ## License: Simplified BSD
4
+
5
+ require 'flox'
6
+ require 'test/unit'
7
+
8
+ class EntityTest < Test::Unit::TestCase
9
+
10
+ def test_init
11
+ type = "type"
12
+ id = "id"
13
+ data = { value: true }
14
+ entity = Flox::Entity.new(type, id, data)
15
+ assert_equal(type, entity.type)
16
+ assert_equal(id, entity.id)
17
+ assert_equal(data[:value], entity[:value])
18
+ assert_not_nil(entity.created_at)
19
+ assert_not_nil(entity.updated_at)
20
+ assert_not_nil(entity.public_access)
21
+ end
22
+
23
+ def test_init_without_id
24
+ entity = Flox::Entity.new("type")
25
+ assert_kind_of(String, entity.id)
26
+ assert_not_empty(entity.id)
27
+ end
28
+
29
+ def test_created_at
30
+ created_at = Time.parse("2014-02-20T11:00:00Z")
31
+ created_at_string = created_at.to_xs_datetime
32
+ entity = Flox::Entity.new('type', 'id')
33
+ entity['createdAt'] = created_at_string
34
+ assert_equal(created_at, entity.created_at)
35
+ end
36
+
37
+ def test_updated_at
38
+ updated_at = Time.parse("2014-02-20T11:00:00Z")
39
+ updated_at_string = updated_at.to_xs_datetime
40
+ entity = Flox::Entity.new('type', 'id')
41
+ entity['updatedAt'] = updated_at_string
42
+ assert_equal(updated_at, entity.updated_at)
43
+ end
44
+
45
+ def test_public_access
46
+ entity = Flox::Entity.new('type', 'id')
47
+ assert_equal('', entity.public_access)
48
+ entity.public_access = 'rw'
49
+ assert_equal('rw', entity.public_access)
50
+ end
51
+
52
+ end
data/test/test_flox.rb ADDED
@@ -0,0 +1,152 @@
1
+ ## Author: Daniel Sperl
2
+ ## Copyright: Copyright 2014 Gamua
3
+ ## License: Simplified BSD
4
+
5
+ require 'flox'
6
+ require 'test/unit'
7
+ require 'mocha/test_unit'
8
+
9
+ class FloxTest < Test::Unit::TestCase
10
+
11
+ GAME_ID = "game_id"
12
+ GAME_KEY = "game_key"
13
+ BASE_URL = "http://url.com"
14
+
15
+ attr_reader :flox
16
+
17
+ def setup
18
+ @flox = Flox.new(GAME_ID, GAME_KEY, BASE_URL)
19
+ end
20
+
21
+ def test_init
22
+ assert_equal(GAME_ID, flox.game_id)
23
+ assert_equal(GAME_KEY, flox.game_key)
24
+ assert_equal(BASE_URL, flox.base_url)
25
+
26
+ assert_kind_of(Flox::Player, flox.current_player)
27
+ assert_equal(:guest, flox.current_player.auth_type)
28
+ end
29
+
30
+ def test_post_score
31
+ flox.service.expects(:post).once
32
+ flox.post_score('leaderboard_id', 123, 'player_name')
33
+ end
34
+
35
+ def test_load_scores
36
+ leaderboard_id = "dummy"
37
+ path = "leaderboards/#{leaderboard_id}"
38
+ raw_score = {
39
+ 'value' => 20,
40
+ 'playerName' => 'hugo',
41
+ 'playerId' => '123',
42
+ 'country' => 'at',
43
+ 'createdAt' => '2014-02-24T20:15:00.123Z'
44
+ }
45
+
46
+ # using time scope (t)
47
+ flox.service.expects(:get).once.with(path, has_key('t')).returns([])
48
+ scores = flox.load_scores(leaderboard_id, :today)
49
+ assert_kind_of(Array, scores)
50
+ assert_equal(0, scores.length)
51
+
52
+ # using player scope (p)
53
+ flox.service.expects(:get).once.with(path, has_key('p')).returns([raw_score])
54
+ scores = flox.load_scores(leaderboard_id, %w(1, 2, 3))
55
+ assert_kind_of(Array, scores)
56
+ assert_equal(1, scores.length)
57
+
58
+ highscore = scores.first
59
+ assert_kind_of(Flox::Score, highscore)
60
+ assert_equal(raw_score['value'], highscore.value)
61
+ assert_equal(raw_score['playerName'], highscore.player_name)
62
+ assert_equal(raw_score['playerId'], highscore.player_id)
63
+ assert_equal(raw_score['country'], highscore.country)
64
+ assert_equal(raw_score['createdAt'], highscore.created_at.to_xs_datetime)
65
+ end
66
+
67
+ def test_login_with_key
68
+ key = "key"
69
+ result = { 'id' => '123', 'entity' => { 'authType' => 'key' } }
70
+ flox.service.expects(:login).with(:key, key, nil).once.returns(result)
71
+ player = flox.login_with_key(key)
72
+ assert_not_nil(player)
73
+ assert_equal(:key, player.auth_type)
74
+ assert_equal(player, flox.current_player)
75
+ end
76
+
77
+ def test_login_guest
78
+ player = flox.login_guest
79
+ assert_not_nil(player)
80
+ assert_equal(:guest, player.auth_type)
81
+ assert_equal(player, flox.current_player)
82
+ end
83
+
84
+ def test_load_entity
85
+ type = "type"
86
+ id = "id"
87
+ path = "entities/#{type}/#{id}"
88
+ data = { "name" => "Jean-Luc" }
89
+
90
+ flox.service.expects(:get).with(path).once.returns(data)
91
+ entity = flox.load_entity(type, id)
92
+
93
+ assert_kind_of(Flox::Entity, entity)
94
+ assert_equal(id, entity.id)
95
+ assert_equal(type, entity.type)
96
+ assert_equal(path, entity.path)
97
+ assert_equal(data["name"], entity["name"])
98
+ end
99
+
100
+ def test_save_entity
101
+ data = { "name" => "Jean-Luc" }
102
+ entity = Flox::Entity.new("type", "id", data)
103
+ path = "entities/#{entity.type}/#{entity.id}"
104
+ result = { "createdAt" => "2014-01-01T12:00:00.000Z",
105
+ "updatedAt" => "2014-02-01T12:00:00.000Z" }
106
+
107
+ flox.service.expects(:put).with(path, entity).once.returns(result)
108
+ flox.save_entity(entity)
109
+
110
+ assert_equal(result["createdAt"], entity.created_at.to_xs_datetime)
111
+ assert_equal(result["updatedAt"], entity.updated_at.to_xs_datetime)
112
+ end
113
+
114
+ def test_delete_entity
115
+ entity = Flox::Entity.new("type", "id")
116
+ path = "entities/#{entity.type}/#{entity.id}"
117
+ flox.service.expects(:delete).with(path)
118
+ flox.delete_entity(entity)
119
+ end
120
+
121
+ def test_find_logs
122
+ log_ids = %w{ 0 1 2 }
123
+ result = { 'ids' => log_ids, 'cursor' => nil }
124
+ flox.service.expects(:get).at_most_once.returns(result)
125
+ logs = flox.load_logs(':warning', 50)
126
+ assert_kind_of(Flox::ResourceEnumerator, logs)
127
+ assert_equal(log_ids.length, logs.length)
128
+ flox.service.expects(:get).times(log_ids.length).returns({})
129
+ logs.each { |log| assert_kind_of(Hash, log) }
130
+ end
131
+
132
+ def test_find_log_ids
133
+ log_ids = %w{ 0 1 2 3 4 5 6 7 8 9 }
134
+ log_ids_a = log_ids.slice 0, 5
135
+ log_ids_b = log_ids.slice 5, 5
136
+ result_a = { 'ids' => log_ids_a, 'cursor' => 'a' }
137
+ result_b = { 'ids' => log_ids_b, 'cursor' => nil }
138
+
139
+ # without limit
140
+ flox.service.expects(:get).twice.returns(result_a, result_b)
141
+ out_log_ids = flox.load_log_ids
142
+ assert_equal(log_ids.length, out_log_ids.length)
143
+
144
+ # with limit
145
+ limit = 7
146
+ result_b['ids'] = %w{ 5 6 }
147
+ flox.service.expects(:get).twice.returns(result_a, result_b)
148
+ out_log_ids = flox.load_log_ids(nil, limit)
149
+ assert_equal(limit, out_log_ids.length)
150
+ end
151
+
152
+ end
@@ -0,0 +1,34 @@
1
+ ## Author: Daniel Sperl
2
+ ## Copyright: Copyright 2014 Gamua
3
+ ## License: Simplified BSD
4
+
5
+ require 'flox'
6
+ require 'test/unit'
7
+
8
+ class EntityTest < Test::Unit::TestCase
9
+
10
+ def test_random_uid
11
+ length = 16
12
+ uid = String.random_uid(length)
13
+ assert_equal(length, uid.length)
14
+ assert_match(/^[a-zA-Z0-9]+$/, uid)
15
+ end
16
+
17
+ def test_xs_datetime
18
+ time_xs = "2014-02-20T11:00:00.124Z"
19
+ time = Time.parse(time_xs)
20
+ assert_equal(time_xs, time.to_xs_datetime)
21
+ end
22
+
23
+ def test_to_camelcase
24
+ assert_equal("homeSweetHome", "home_sweet_home".to_camelcase)
25
+ assert_equal("homeSweetHome", "hOME-sWEET-hOME".to_camelcase)
26
+ assert_equal("homeSweetHome", "Home Sweet Home".to_camelcase)
27
+ end
28
+
29
+ def test_to_underscore
30
+ assert_equal("home_sweet_home", "HomeSweetHome".to_underscore)
31
+ assert_equal("home_sweet_home", "homeSweetHome".to_underscore)
32
+ end
33
+
34
+ end
metadata ADDED
@@ -0,0 +1,102 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: flox
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Daniel Sperl
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-02-21 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: json
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: mocha
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: yard
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '0.8'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '0.8'
55
+ description: |
56
+ Flox is the no-fuzz backend for game developers. The Ruby SDK allows direct
57
+ interaction with the Flox servers, e.g. to download log files or update
58
+ specific entities. It can be used from other Ruby scripts or directly with
59
+ its bundled command-line utility.
60
+ email: daniel@gamua.com
61
+ executables: []
62
+ extensions: []
63
+ extra_rdoc_files: []
64
+ files:
65
+ - bin/flox
66
+ - test/test_entity.rb
67
+ - test/test_flox.rb
68
+ - test/test_utils.rb
69
+ - lib/flox/entity.rb
70
+ - lib/flox/player.rb
71
+ - lib/flox/resource_enumerator.rb
72
+ - lib/flox/rest_service.rb
73
+ - lib/flox/score.rb
74
+ - lib/flox/utils.rb
75
+ - lib/flox/version.rb
76
+ - lib/flox.rb
77
+ homepage: https://www.flox.cc
78
+ licenses:
79
+ - Simplified BSD
80
+ metadata: {}
81
+ post_install_message:
82
+ rdoc_options: []
83
+ require_paths:
84
+ - lib
85
+ required_ruby_version: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ required_rubygems_version: !ruby/object:Gem::Requirement
91
+ requirements:
92
+ - - ">="
93
+ - !ruby/object:Gem::Version
94
+ version: '0'
95
+ requirements: []
96
+ rubyforge_project:
97
+ rubygems_version: 2.0.3
98
+ signing_key:
99
+ specification_version: 4
100
+ summary: Ruby SDK for the flox.cc game backend
101
+ test_files: []
102
+ has_rdoc: