flox 0.1.2 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/bin/flox +1 -1
- data/lib/flox.rb +78 -32
- data/lib/flox/entity.rb +24 -11
- data/lib/flox/errors.rb +20 -0
- data/lib/flox/player.rb +3 -3
- data/lib/flox/query.rb +84 -0
- data/lib/flox/rest_service.rb +80 -50
- data/lib/flox/score.rb +5 -5
- data/lib/flox/version.rb +1 -1
- data/test/test_entity.rb +13 -2
- data/test/test_flox.rb +32 -32
- data/test/test_query.rb +70 -0
- metadata +4 -2
- data/lib/flox/resource_enumerator.rb +0 -43
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8c588236a3e6206950c5d693305514fe02335a0e
|
4
|
+
data.tar.gz: 9344c387fa0f1ff1a6060b8bff84cb1fdcdc485d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ade435b67774267ec23f818078a756525a542ae1bc7ca7205d4a80e22909eda3e1d45121935a7ee430bdf91dbf4ecb5495e2f5d4f0238de7fd747febe2ae3af8
|
7
|
+
data.tar.gz: cd5754f39054ebf0b8d9cbce960b3c5c6850ee931ff92b3ddc35d58e94d83d2af3a9e38d0b8b98332ff5dbec9c66542691e4edd29e5421b19b03fa0ecafed720
|
data/README.md
CHANGED
@@ -53,7 +53,7 @@ We're done with the preparations! Now let's look at some of the things you can d
|
|
53
53
|
entity = flox.load_entity('entity-type', 'entity-id') # => Flox::Entity
|
54
54
|
|
55
55
|
# modify it via the []-operator
|
56
|
-
entity[
|
56
|
+
entity[:myProperty] = 'something'
|
57
57
|
|
58
58
|
# save or delete at will
|
59
59
|
flox.save_entity(entity)
|
data/bin/flox
CHANGED
@@ -32,7 +32,7 @@ class Worker
|
|
32
32
|
FileUtils.mkdir_p(destination)
|
33
33
|
|
34
34
|
print "Loading log IDs (this could take a moment) ... "
|
35
|
-
log_ids = flox.
|
35
|
+
log_ids = flox.find_log_ids(query, limit)
|
36
36
|
puts "found #{log_ids.length} logs."
|
37
37
|
log_ids.each do |log_id|
|
38
38
|
remote_path = "logs/#{log_id}"
|
data/lib/flox.rb
CHANGED
@@ -8,9 +8,6 @@
|
|
8
8
|
# of all players.
|
9
9
|
class Flox
|
10
10
|
|
11
|
-
# The main Error class, all Exception classes inherit from this class.
|
12
|
-
class Error < StandardError; end
|
13
|
-
|
14
11
|
# The URL where the Flox servers are found.
|
15
12
|
DEFAULT_URL = "https://www.flox.cc"
|
16
13
|
|
@@ -49,7 +46,7 @@ class Flox
|
|
49
46
|
# @private
|
50
47
|
def login(auth_type, auth_id=nil, auth_token=nil)
|
51
48
|
data = service.login(auth_type, auth_id, auth_token)
|
52
|
-
@current_player = Player.new(data[
|
49
|
+
@current_player = Player.new(data[:id], data[:entity])
|
53
50
|
end
|
54
51
|
|
55
52
|
# Loads an entity with a certain type and id from the server.
|
@@ -58,11 +55,7 @@ class Flox
|
|
58
55
|
def load_entity(type, id)
|
59
56
|
path = entity_path(type, id)
|
60
57
|
data = service.get(path)
|
61
|
-
|
62
|
-
Player.new(id, data)
|
63
|
-
else
|
64
|
-
Entity.new(type, id, data)
|
65
|
-
end
|
58
|
+
create_entity(type, id, data)
|
66
59
|
end
|
67
60
|
|
68
61
|
# Loads an entity with type '.player'.
|
@@ -75,8 +68,8 @@ class Flox
|
|
75
68
|
# @return [Flox::Entity]
|
76
69
|
def save_entity(entity)
|
77
70
|
result = service.put(entity.path, entity)
|
78
|
-
entity[
|
79
|
-
entity[
|
71
|
+
entity[:updatedAt] = result[:updatedAt]
|
72
|
+
entity[:createdAt] = result[:createdAt]
|
80
73
|
entity
|
81
74
|
end
|
82
75
|
|
@@ -112,9 +105,9 @@ class Flox
|
|
112
105
|
args = {}
|
113
106
|
|
114
107
|
if scope.is_a?(Array)
|
115
|
-
args[
|
108
|
+
args[:p] = scope
|
116
109
|
else
|
117
|
-
args[
|
110
|
+
args[:t] = scope.to_s.to_camelcase
|
118
111
|
end
|
119
112
|
|
120
113
|
raw_scores = service.get(path, args)
|
@@ -128,15 +121,26 @@ class Flox
|
|
128
121
|
service.get(path, args)
|
129
122
|
end
|
130
123
|
|
124
|
+
# Loads a bulk of resources from a certain path, optionally feeding them
|
125
|
+
# through a block.
|
126
|
+
# @yield [id, resource] called on every resource; the return-value is feeded
|
127
|
+
# into the result array.
|
128
|
+
# @return [Array]
|
129
|
+
def load_resources(path, ids)
|
130
|
+
ids.map do |id|
|
131
|
+
resource = load_resource "#{path}/#{id}"
|
132
|
+
resource = yield id, resource if block_given?
|
133
|
+
resource
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
131
137
|
# Loads a log with a certain ID. A log is a Hash instance.
|
132
138
|
# @return [Hash]
|
133
139
|
def load_log(log_id)
|
134
|
-
|
135
|
-
log['id'] = log_id unless log['id']
|
136
|
-
log
|
140
|
+
service.get log_path(log_id)
|
137
141
|
end
|
138
142
|
|
139
|
-
#
|
143
|
+
# Finds logs defined by a certain query.
|
140
144
|
# Here are some sample queries:
|
141
145
|
#
|
142
146
|
# * `day:2014-02-20` → all logs of a certain day
|
@@ -144,32 +148,59 @@ class Flox
|
|
144
148
|
# * `severity:error` → all logs of type error
|
145
149
|
# * `day:2014-02-20 severity:error` → all error logs from February 20th.
|
146
150
|
#
|
147
|
-
# @return [
|
148
|
-
def
|
149
|
-
log_ids =
|
150
|
-
|
151
|
-
ResourceEnumerator.new(service, paths)
|
151
|
+
# @return [Array<Hash>]
|
152
|
+
def find_logs(query=nil, limit=nil)
|
153
|
+
log_ids = find_log_ids(query, limit)
|
154
|
+
load_resources('logs', log_ids)
|
152
155
|
end
|
153
156
|
|
154
|
-
#
|
157
|
+
# Finds just the IDs of the logs, defined by a certain query.
|
158
|
+
# @see {#find_logs}
|
155
159
|
# @return [Array<String>]
|
156
|
-
def
|
160
|
+
def find_log_ids(query=nil, limit=nil)
|
157
161
|
log_ids = []
|
158
162
|
cursor = nil
|
159
163
|
begin
|
160
164
|
args = {}
|
161
|
-
args[
|
162
|
-
args[
|
163
|
-
args[
|
165
|
+
args[:q] = query if query
|
166
|
+
args[:l] = limit if limit
|
167
|
+
args[:c] = cursor if cursor
|
164
168
|
|
165
169
|
result = service.get "logs", args
|
166
|
-
cursor = result[
|
167
|
-
log_ids += result[
|
170
|
+
cursor = result[:cursor]
|
171
|
+
log_ids += result[:ids]
|
168
172
|
limit -= log_ids.length if limit
|
169
173
|
end while !cursor.nil? and (limit.nil? or limit > 0)
|
170
174
|
log_ids
|
171
175
|
end
|
172
176
|
|
177
|
+
# Executes a query over Entities, using a simple SQL-like syntax.
|
178
|
+
# @see Flox::Query
|
179
|
+
#
|
180
|
+
# @overload find_entities(query)
|
181
|
+
# @param query [Flox::Query] the query to execute
|
182
|
+
# @overload find_entities(entity_type, constraints=nil, *args)
|
183
|
+
# @param type [String, Symbol, Class] the type of the entity
|
184
|
+
# @param query [String] the query string with optional '?' placeholders
|
185
|
+
def find_entities(*query)
|
186
|
+
query = create_query(*query)
|
187
|
+
ids = find_entity_ids(query)
|
188
|
+
load_resources "entities/#{query.type}", ids do |id, data|
|
189
|
+
create_entity(query.type, id, data)
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
# Executes a query over Entities, using a simple SQL-like syntax.
|
194
|
+
# @return [Array<String>]
|
195
|
+
# @see #find_entities
|
196
|
+
# @see Flox::Query
|
197
|
+
def find_entity_ids(*query)
|
198
|
+
query = create_query(*query)
|
199
|
+
path = "entities/#{query.type}"
|
200
|
+
data = { where: query.constraints, offset: query.offset, limit: query.limit }
|
201
|
+
service.post(path, data).map {|e| e[:id]}
|
202
|
+
end
|
203
|
+
|
173
204
|
# Loads the status of the Flox service.
|
174
205
|
# @return [Hash] with the keys 'status' and 'version'.
|
175
206
|
def status
|
@@ -213,12 +244,27 @@ class Flox
|
|
213
244
|
"logs/#{log_id}"
|
214
245
|
end
|
215
246
|
|
247
|
+
def create_entity(type, id, data)
|
248
|
+
if (type == '.player' or type == Flox::Player)
|
249
|
+
Player.new(id, data)
|
250
|
+
else
|
251
|
+
Entity.new(type, id, data)
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
def create_query(*query)
|
256
|
+
if query[0].kind_of? Flox::Query then query[0]
|
257
|
+
else Flox::Query.new(query[0], query[1], *query[2..-1])
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
216
261
|
end
|
217
262
|
|
218
|
-
require 'flox/version'
|
219
|
-
require 'flox/utils'
|
220
263
|
require 'flox/rest_service'
|
264
|
+
require 'flox/version'
|
265
|
+
require 'flox/errors'
|
221
266
|
require 'flox/entity'
|
222
267
|
require 'flox/player'
|
223
268
|
require 'flox/score'
|
224
|
-
require 'flox/
|
269
|
+
require 'flox/query'
|
270
|
+
require 'flox/utils'
|
data/lib/flox/entity.rb
CHANGED
@@ -10,7 +10,7 @@ require 'time'
|
|
10
10
|
# The class extends `Hash`. Thus, all properties of the Entity can be accessed
|
11
11
|
# as keys of the Entity instance.
|
12
12
|
#
|
13
|
-
# entity[
|
13
|
+
# entity[:name] = 'Donald Duck'
|
14
14
|
#
|
15
15
|
# For convenience, the standard entity properties (e.g. `created_at` and
|
16
16
|
# `updated_at`)can be accessed via Ruby attributes.
|
@@ -19,7 +19,7 @@ require 'time'
|
|
19
19
|
#
|
20
20
|
# To load and save an entity, use the respective methods on the Flox class.
|
21
21
|
#
|
22
|
-
# my_entity = flox.load_entity(
|
22
|
+
# my_entity = flox.load_entity(:SaveGame, '12345') # => Flox::Entity
|
23
23
|
# flox.save_entity(my_entity)
|
24
24
|
#
|
25
25
|
class Flox::Entity < Hash
|
@@ -36,33 +36,36 @@ class Flox::Entity < Hash
|
|
36
36
|
def initialize(type, id=nil, data=nil)
|
37
37
|
@type = type
|
38
38
|
@id = id ? id : String.random_uid
|
39
|
-
self[
|
40
|
-
self
|
41
|
-
|
39
|
+
self[:createdAt] = self[:updatedAt] = Time.now.utc.to_xs_datetime
|
40
|
+
self[:publicAccess] = ''
|
41
|
+
if (data)
|
42
|
+
data_sym = Hash[data.map{|k, v| [k.to_sym, v]}]
|
43
|
+
self.merge!(data_sym)
|
44
|
+
end
|
42
45
|
end
|
43
46
|
|
44
47
|
def created_at
|
45
|
-
Time.parse self[
|
48
|
+
Time.parse self[:createdAt]
|
46
49
|
end
|
47
50
|
|
48
51
|
def updated_at
|
49
|
-
Time.parse self[
|
52
|
+
Time.parse self[:updatedAt]
|
50
53
|
end
|
51
54
|
|
52
55
|
def public_access
|
53
|
-
self[
|
56
|
+
self[:publicAccess]
|
54
57
|
end
|
55
58
|
|
56
59
|
def public_access=(access)
|
57
|
-
self[
|
60
|
+
self[:publicAccess] = access.to_s
|
58
61
|
end
|
59
62
|
|
60
63
|
def owner_id
|
61
|
-
self[
|
64
|
+
self[:ownerId]
|
62
65
|
end
|
63
66
|
|
64
67
|
def owner_id=(value)
|
65
|
-
self[
|
68
|
+
self[:ownerId] = value.to_s
|
66
69
|
end
|
67
70
|
|
68
71
|
def path
|
@@ -78,6 +81,16 @@ class Flox::Entity < Hash
|
|
78
81
|
description += "]"
|
79
82
|
end
|
80
83
|
|
84
|
+
# Accesses a property of the entity; both symbols and strings work.
|
85
|
+
def [](key)
|
86
|
+
super(key.to_sym)
|
87
|
+
end
|
88
|
+
|
89
|
+
# (see #[])
|
90
|
+
def []=(key, value)
|
91
|
+
super(key.to_sym, value)
|
92
|
+
end
|
93
|
+
|
81
94
|
#
|
82
95
|
# documentation hints
|
83
96
|
#
|
data/lib/flox/errors.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
## Author: Daniel Sperl
|
2
|
+
## Copyright: Copyright 2014 Gamua
|
3
|
+
## License: Simplified BSD
|
4
|
+
|
5
|
+
# The main Error class for Flox errors
|
6
|
+
class Flox::Error < StandardError; end
|
7
|
+
|
8
|
+
# Raised when the REST service encounters an error
|
9
|
+
# (e.g. the server returns a HTTP code >= 400).
|
10
|
+
class Flox::ServiceError < Flox::Error
|
11
|
+
|
12
|
+
# @return [Net::HTTPResponse] the complete server response
|
13
|
+
attr_reader :response
|
14
|
+
|
15
|
+
# @param code [String] the http status code
|
16
|
+
def initialize(response)
|
17
|
+
@response = response
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
data/lib/flox/player.rb
CHANGED
@@ -23,14 +23,14 @@ class Flox::Player < Flox::Entity
|
|
23
23
|
# is always `.player` in Flox.
|
24
24
|
def initialize(id=nil, data=nil)
|
25
25
|
data ||= {}
|
26
|
-
data[
|
27
|
-
data[
|
26
|
+
data[:authType] ||= "guest"
|
27
|
+
data[:publicAccess] ||= "r"
|
28
28
|
super(".player", id, data)
|
29
29
|
self.owner_id ||= self.id
|
30
30
|
end
|
31
31
|
|
32
32
|
def auth_type
|
33
|
-
self[
|
33
|
+
self[:authType].to_sym
|
34
34
|
end
|
35
35
|
|
36
36
|
#
|
data/lib/flox/query.rb
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
## Author: Daniel Sperl
|
2
|
+
## Copyright: Copyright 2014 Gamua
|
3
|
+
## License: Simplified BSD
|
4
|
+
|
5
|
+
# The Query class allows you to retrieve entities from the server by narrowing
|
6
|
+
# down the results with certain constraints. The system works similar to SQL
|
7
|
+
# "select" statements.
|
8
|
+
#
|
9
|
+
# Before you can make a query, you have to create indices that match the query.
|
10
|
+
# You can do that in the Flox online interface. An index has to contain all the
|
11
|
+
# properties that are referenced in the constraints.
|
12
|
+
#
|
13
|
+
# Here is an example of how you can execute a Query with Flox. This query
|
14
|
+
# requires an index containing both "level" and "score" properties.
|
15
|
+
#
|
16
|
+
# query = Query.new(:Player)
|
17
|
+
# query.where("level == ? AND score > ?", "tutorial", 500)
|
18
|
+
# query.limit = 1
|
19
|
+
# results = flox.find_entities(query)
|
20
|
+
#
|
21
|
+
# Alternatively, you can execute the query in one single line:
|
22
|
+
#
|
23
|
+
# results = flox.find_entities(:Player, "score > ?", 500)
|
24
|
+
# puts "Found #{results.length} players"
|
25
|
+
#
|
26
|
+
class Flox::Query
|
27
|
+
|
28
|
+
# @return [String] the constraints that will be used as WHERE-clause.
|
29
|
+
# It is recommended to use the {#where} method to construct them.
|
30
|
+
attr_accessor :constraints
|
31
|
+
|
32
|
+
# @return [Fixnum] the offset of the results returned by the query.
|
33
|
+
attr_accessor :offset
|
34
|
+
|
35
|
+
# @return [Fixnum] the maximum number of returned entities.
|
36
|
+
attr_accessor :limit
|
37
|
+
|
38
|
+
# @return [String] the entity type that is searched.
|
39
|
+
attr_reader :type
|
40
|
+
|
41
|
+
# Create a new query that will search within the given Entity type.
|
42
|
+
# Optionally, pass the constraints in the same way as in the {#where} method.
|
43
|
+
# @param entity_type [String, Symbol, Class] the type of the entity
|
44
|
+
def initialize(entity_type, constraints=nil, *args)
|
45
|
+
if entity_type == Flox::Player || entity_type.to_s == "Player"
|
46
|
+
entity_type = '.player'
|
47
|
+
end
|
48
|
+
|
49
|
+
@type = entity_type
|
50
|
+
@offset = 0
|
51
|
+
@limit = 50
|
52
|
+
where(constraints, *args) if constraints
|
53
|
+
end
|
54
|
+
|
55
|
+
# You can narrow down the results of the query with an SQL like where-clause.
|
56
|
+
# The constraints string supports the following comparison operators:
|
57
|
+
# `==, >, >=, <, <=, !=`. You can combine constraints using `AND` and `OR`;
|
58
|
+
# construct logical groups with round brackets.
|
59
|
+
#
|
60
|
+
# To simplify creation of the constraints string, you can use questions
|
61
|
+
# marks as placeholders. They will be replaced one by one with the additional
|
62
|
+
# parameters you pass to the method, while making sure their format is correct
|
63
|
+
# (e.g. it surrounds Strings with quotations marks). Here is an example:
|
64
|
+
#
|
65
|
+
# query.where("name == ? AND score > ?", "thomas", 500);
|
66
|
+
# # -> name == "thomas" AND score > 500
|
67
|
+
#
|
68
|
+
# Use the 'IN'-operator to check for inclusion within a list of possible values:
|
69
|
+
#
|
70
|
+
# query.where("name IN ?", ["alfa", "bravo", "charlie"]);
|
71
|
+
# # -> name IN ["alfa", "bravo", "charlie"]
|
72
|
+
#
|
73
|
+
# Note that subsequent calls to this method will replace preceding constraints.
|
74
|
+
# @return String the final constraints string
|
75
|
+
def where(constraints, *args)
|
76
|
+
@constraints = constraints.gsub(/\?/) do
|
77
|
+
raise ArgumentError, "incorrect placeholder count" unless args.length > 0
|
78
|
+
arg = args.shift
|
79
|
+
arg = arg.to_xs_datetime if arg.kind_of?(Time)
|
80
|
+
arg.to_json
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
data/lib/flox/rest_service.rb
CHANGED
@@ -30,33 +30,90 @@ class Flox::RestService
|
|
30
30
|
# and added to the path.
|
31
31
|
# @return the server response.
|
32
32
|
def get(path, data=nil)
|
33
|
-
path
|
34
|
-
path += "?" + URI.encode_www_form(data) if data
|
35
|
-
request = Net::HTTP::Get.new(path)
|
36
|
-
execute(request)
|
33
|
+
request(:get, path, data)
|
37
34
|
end
|
38
35
|
|
39
36
|
# Makes a `DELETE` request at the server.
|
40
37
|
# @return the server response.
|
41
38
|
def delete(path)
|
42
|
-
request
|
43
|
-
execute(request)
|
39
|
+
request(:delete, path)
|
44
40
|
end
|
45
41
|
|
46
42
|
# Makes a `POST` request at the server. The given data-Hash is transferred
|
47
43
|
# in the body of the request.
|
48
44
|
# @return the server response.
|
49
45
|
def post(path, data=nil)
|
50
|
-
request
|
51
|
-
execute(request, data)
|
46
|
+
request(:post, path, data)
|
52
47
|
end
|
53
48
|
|
54
49
|
# Makes a `PUT` request at the server. The given data-Hash is transferred
|
55
50
|
# in the body of the request.
|
56
51
|
# @return the server response.
|
57
52
|
def put(path, data=nil)
|
58
|
-
request
|
59
|
-
|
53
|
+
request(:put, path, data)
|
54
|
+
end
|
55
|
+
|
56
|
+
# Makes a request on the Flox server. When called without a block, the
|
57
|
+
# method will raise a {Flox::ServiceError} if the server returns an HTTP
|
58
|
+
# error code; when called with a block, it will always succeed.
|
59
|
+
#
|
60
|
+
# @yield [body, response] The decoded body and the raw http response
|
61
|
+
# @param method [Symbol] one of `:get, :delete, :put, :post`
|
62
|
+
# @param path [String] the path relative to the game, e.g. "entities/product"
|
63
|
+
# @param data [Hash] the body of the request.
|
64
|
+
# @return the body of the server response
|
65
|
+
def request(method, path, data=nil)
|
66
|
+
request_class =
|
67
|
+
case method
|
68
|
+
when :get
|
69
|
+
if data
|
70
|
+
path += "?" + URI.encode_www_form(data)
|
71
|
+
data = nil
|
72
|
+
end
|
73
|
+
Net::HTTP::Get
|
74
|
+
when :delete then Net::HTTP::Delete
|
75
|
+
when :post then Net::HTTP::Post
|
76
|
+
when :put then Net::HTTP::Put
|
77
|
+
end
|
78
|
+
|
79
|
+
flox_header = {
|
80
|
+
:sdk => { :type => "ruby", :version => Flox::VERSION },
|
81
|
+
:gameKey => @game_key,
|
82
|
+
:dispatchTime => Time.now.utc.to_xs_datetime,
|
83
|
+
:bodyCompression => "zlib",
|
84
|
+
:player => @authentication
|
85
|
+
}
|
86
|
+
|
87
|
+
request = request_class.new(full_path(path))
|
88
|
+
request["Content-Type"] = "application/json"
|
89
|
+
request["X-Flox"] = flox_header.to_json
|
90
|
+
request.body = encode(data.to_json) if data
|
91
|
+
|
92
|
+
uri = URI.parse(@base_url)
|
93
|
+
http = Net::HTTP::new(uri.host, uri.port)
|
94
|
+
|
95
|
+
if uri.scheme == "https" # enable SSL/TLS
|
96
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
97
|
+
http.use_ssl = true
|
98
|
+
end
|
99
|
+
|
100
|
+
http.start do |session|
|
101
|
+
response = session.request(request)
|
102
|
+
body = body_from_response response
|
103
|
+
|
104
|
+
if block_given?
|
105
|
+
# if a block was passed to the method, no error is thrown
|
106
|
+
yield body, response
|
107
|
+
return body
|
108
|
+
else
|
109
|
+
# without a block, we raise an error if the request was not a success
|
110
|
+
if (response.is_a? Net::HTTPSuccess)
|
111
|
+
return body
|
112
|
+
else
|
113
|
+
raise Flox::ServiceError.new(response), (body[:message] rescue body)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
60
117
|
end
|
61
118
|
|
62
119
|
# Makes a login on the server with the given authentication data.
|
@@ -76,7 +133,7 @@ class Flox::RestService
|
|
76
133
|
auth_data[:id] = @authentication[:id]
|
77
134
|
end
|
78
135
|
response = post("authenticate", auth_data)
|
79
|
-
auth_data[:id] = response[
|
136
|
+
auth_data[:id] = response[:id]
|
80
137
|
end
|
81
138
|
|
82
139
|
@authentication = auth_data
|
@@ -90,45 +147,6 @@ class Flox::RestService
|
|
90
147
|
|
91
148
|
private
|
92
149
|
|
93
|
-
def execute(request, data=nil)
|
94
|
-
flox_header = {
|
95
|
-
"sdk" => { "type" => "ruby", "version" => Flox::VERSION },
|
96
|
-
"gameKey" => @game_key,
|
97
|
-
"dispatchTime" => Time.now.utc.to_xs_datetime,
|
98
|
-
"bodyCompression" => "zlib",
|
99
|
-
"player" => @authentication
|
100
|
-
}
|
101
|
-
|
102
|
-
request["Content-Type"] = "application/json"
|
103
|
-
request["X-Flox"] = flox_header.to_json
|
104
|
-
request.body = encode(data.to_json) if data
|
105
|
-
|
106
|
-
uri = URI.parse(@base_url)
|
107
|
-
http = Net::HTTP::new(uri.host, uri.port)
|
108
|
-
|
109
|
-
if uri.scheme == "https" # enable SSL/TLS
|
110
|
-
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
111
|
-
http.use_ssl = true
|
112
|
-
end
|
113
|
-
|
114
|
-
http.start do |session|
|
115
|
-
response = session.request(request)
|
116
|
-
body = response.body
|
117
|
-
body = decode(body) if response['x-content-encoding'] == 'zlib'
|
118
|
-
|
119
|
-
if (response.is_a? Net::HTTPSuccess)
|
120
|
-
return JSON.parse(body || '{}')
|
121
|
-
else
|
122
|
-
message = begin
|
123
|
-
JSON.parse(body)['message']
|
124
|
-
rescue
|
125
|
-
body
|
126
|
-
end
|
127
|
-
raise Flox::Error, message
|
128
|
-
end
|
129
|
-
end
|
130
|
-
end
|
131
|
-
|
132
150
|
def decode(string)
|
133
151
|
return nil if string.nil? or string.empty?
|
134
152
|
Zlib::Inflate.inflate(Base64.decode64(string))
|
@@ -139,6 +157,18 @@ class Flox::RestService
|
|
139
157
|
Base64.encode64(Zlib::Deflate.deflate(string))
|
140
158
|
end
|
141
159
|
|
160
|
+
def body_from_response(response)
|
161
|
+
body = response.body
|
162
|
+
|
163
|
+
begin
|
164
|
+
body = decode(response.body) if response['x-content-encoding'] == 'zlib'
|
165
|
+
JSON.parse(body || '{}', {symbolize_names: true})
|
166
|
+
rescue
|
167
|
+
html_matches = /\<h1\>(.*)\<\/h1\>/.match(body)
|
168
|
+
{ message: html_matches ? html_matches[1] : body }
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
142
172
|
def full_path(path)
|
143
173
|
"/api/games/#{@game_id}/#{path}"
|
144
174
|
end
|
data/lib/flox/score.rb
CHANGED
@@ -24,11 +24,11 @@ class Flox::Score
|
|
24
24
|
|
25
25
|
# @param data [Hash] the contents of the score as given by the Flox server.
|
26
26
|
def initialize(data)
|
27
|
-
@player_id = data[
|
28
|
-
@player_name = data[
|
29
|
-
@value = data[
|
30
|
-
@country = data[
|
31
|
-
@created_at = Time.parse(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
32
|
end
|
33
33
|
|
34
34
|
end
|
data/lib/flox/version.rb
CHANGED
data/test/test_entity.rb
CHANGED
@@ -30,7 +30,7 @@ class EntityTest < Test::Unit::TestCase
|
|
30
30
|
created_at = Time.parse("2014-02-20T11:00:00Z")
|
31
31
|
created_at_string = created_at.to_xs_datetime
|
32
32
|
entity = Flox::Entity.new('type', 'id')
|
33
|
-
entity[
|
33
|
+
entity[:createdAt] = created_at_string
|
34
34
|
assert_equal(created_at, entity.created_at)
|
35
35
|
end
|
36
36
|
|
@@ -38,7 +38,7 @@ class EntityTest < Test::Unit::TestCase
|
|
38
38
|
updated_at = Time.parse("2014-02-20T11:00:00Z")
|
39
39
|
updated_at_string = updated_at.to_xs_datetime
|
40
40
|
entity = Flox::Entity.new('type', 'id')
|
41
|
-
entity[
|
41
|
+
entity[:updatedAt] = updated_at_string
|
42
42
|
assert_equal(updated_at, entity.updated_at)
|
43
43
|
end
|
44
44
|
|
@@ -49,4 +49,15 @@ class EntityTest < Test::Unit::TestCase
|
|
49
49
|
assert_equal('rw', entity.public_access)
|
50
50
|
end
|
51
51
|
|
52
|
+
def test_accepts_string_or_symbol
|
53
|
+
first_name = 'donald'
|
54
|
+
last_name = 'duck'
|
55
|
+
entity = Flox::Entity.new('type', 'id',
|
56
|
+
{ first_name: first_name, "last_name" => last_name })
|
57
|
+
assert_equal(first_name, entity[:first_name])
|
58
|
+
assert_equal(first_name, entity['first_name'])
|
59
|
+
assert_equal(last_name, entity[:last_name])
|
60
|
+
assert_equal(last_name, entity['last_name'])
|
61
|
+
end
|
62
|
+
|
52
63
|
end
|
data/test/test_flox.rb
CHANGED
@@ -36,37 +36,37 @@ class FloxTest < Test::Unit::TestCase
|
|
36
36
|
leaderboard_id = "dummy"
|
37
37
|
path = "leaderboards/#{leaderboard_id}"
|
38
38
|
raw_score = {
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
39
|
+
:value => 20,
|
40
|
+
:playerName => 'hugo',
|
41
|
+
:playerId => '123',
|
42
|
+
:country => 'at',
|
43
|
+
:createdAt => '2014-02-24T20:15:00.123Z'
|
44
44
|
}
|
45
45
|
|
46
46
|
# using time scope (t)
|
47
|
-
flox.service.expects(:get).once.with(path, has_key(
|
47
|
+
flox.service.expects(:get).once.with(path, has_key(:t)).returns([])
|
48
48
|
scores = flox.load_scores(leaderboard_id, :today)
|
49
49
|
assert_kind_of(Array, scores)
|
50
50
|
assert_equal(0, scores.length)
|
51
51
|
|
52
52
|
# using player scope (p)
|
53
|
-
flox.service.expects(:get).once.with(path, has_key(
|
53
|
+
flox.service.expects(:get).once.with(path, has_key(:p)).returns([raw_score])
|
54
54
|
scores = flox.load_scores(leaderboard_id, %w(1, 2, 3))
|
55
55
|
assert_kind_of(Array, scores)
|
56
56
|
assert_equal(1, scores.length)
|
57
57
|
|
58
58
|
highscore = scores.first
|
59
59
|
assert_kind_of(Flox::Score, highscore)
|
60
|
-
assert_equal(raw_score[
|
61
|
-
assert_equal(raw_score[
|
62
|
-
assert_equal(raw_score[
|
63
|
-
assert_equal(raw_score[
|
64
|
-
assert_equal(raw_score[
|
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
65
|
end
|
66
66
|
|
67
67
|
def test_login_with_key
|
68
68
|
key = "key"
|
69
|
-
result = {
|
69
|
+
result = { :id => '123', :entity => { :authType => 'key' } }
|
70
70
|
flox.service.expects(:login).with(:key, key, nil).once.returns(result)
|
71
71
|
player = flox.login_with_key(key)
|
72
72
|
assert_not_nil(player)
|
@@ -86,7 +86,7 @@ class FloxTest < Test::Unit::TestCase
|
|
86
86
|
type = "type"
|
87
87
|
id = "id"
|
88
88
|
path = "entities/#{type}/#{id}"
|
89
|
-
data = {
|
89
|
+
data = { :name => "Jean-Luc" }
|
90
90
|
|
91
91
|
flox.service.expects(:get).with(path).once.returns(data)
|
92
92
|
entity = flox.load_entity(type, id)
|
@@ -95,32 +95,32 @@ class FloxTest < Test::Unit::TestCase
|
|
95
95
|
assert_equal(id, entity.id)
|
96
96
|
assert_equal(type, entity.type)
|
97
97
|
assert_equal(path, entity.path)
|
98
|
-
assert_equal(data[
|
98
|
+
assert_equal(data[:name], entity[:name])
|
99
99
|
end
|
100
100
|
|
101
101
|
def test_load_player
|
102
102
|
id = "id"
|
103
|
-
data = {
|
103
|
+
data = { :name => "Jean-Luc" }
|
104
104
|
flox.service.expects(:get).once.returns(data)
|
105
105
|
player = flox.load_player(id)
|
106
106
|
assert_kind_of(Flox::Player, player)
|
107
|
-
assert_equal(data[
|
107
|
+
assert_equal(data[:name], player[:name])
|
108
108
|
assert_equal(id, player.id)
|
109
109
|
assert_equal('.player', player.type)
|
110
110
|
end
|
111
111
|
|
112
112
|
def test_save_entity
|
113
|
-
data = {
|
113
|
+
data = { :name => "Jean-Luc" }
|
114
114
|
entity = Flox::Entity.new("type", "id", data)
|
115
115
|
path = "entities/#{entity.type}/#{entity.id}"
|
116
|
-
result = {
|
117
|
-
|
116
|
+
result = { :createdAt => "2014-01-01T12:00:00.000Z",
|
117
|
+
:updatedAt => "2014-02-01T12:00:00.000Z" }
|
118
118
|
|
119
119
|
flox.service.expects(:put).with(path, entity).once.returns(result)
|
120
120
|
flox.save_entity(entity)
|
121
121
|
|
122
|
-
assert_equal(result[
|
123
|
-
assert_equal(result[
|
122
|
+
assert_equal(result[:createdAt], entity.created_at.to_xs_datetime)
|
123
|
+
assert_equal(result[:updatedAt], entity.updated_at.to_xs_datetime)
|
124
124
|
end
|
125
125
|
|
126
126
|
def test_delete_entity
|
@@ -132,12 +132,12 @@ class FloxTest < Test::Unit::TestCase
|
|
132
132
|
|
133
133
|
def test_find_logs
|
134
134
|
log_ids = %w{ 0 1 2 }
|
135
|
-
result = {
|
136
|
-
|
137
|
-
|
138
|
-
|
135
|
+
result = { :ids => log_ids, :cursor => nil }
|
136
|
+
|
137
|
+
flox.service.expects(:get).times(4).returns(result).then.returns({})
|
138
|
+
logs = flox.find_logs('severity:warning', 50)
|
139
|
+
assert_kind_of(Array, logs)
|
139
140
|
assert_equal(log_ids.length, logs.length)
|
140
|
-
flox.service.expects(:get).times(log_ids.length).returns({})
|
141
141
|
logs.each { |log| assert_kind_of(Hash, log) }
|
142
142
|
end
|
143
143
|
|
@@ -145,19 +145,19 @@ class FloxTest < Test::Unit::TestCase
|
|
145
145
|
log_ids = %w{ 0 1 2 3 4 5 6 7 8 9 }
|
146
146
|
log_ids_a = log_ids.slice 0, 5
|
147
147
|
log_ids_b = log_ids.slice 5, 5
|
148
|
-
result_a = {
|
149
|
-
result_b = {
|
148
|
+
result_a = { :ids => log_ids_a, :cursor => 'a' }
|
149
|
+
result_b = { :ids => log_ids_b, :cursor => nil }
|
150
150
|
|
151
151
|
# without limit
|
152
152
|
flox.service.expects(:get).twice.returns(result_a, result_b)
|
153
|
-
out_log_ids = flox.
|
153
|
+
out_log_ids = flox.find_log_ids
|
154
154
|
assert_equal(log_ids.length, out_log_ids.length)
|
155
155
|
|
156
156
|
# with limit
|
157
157
|
limit = 7
|
158
|
-
result_b[
|
158
|
+
result_b[:ids] = %w{ 5 6 }
|
159
159
|
flox.service.expects(:get).twice.returns(result_a, result_b)
|
160
|
-
out_log_ids = flox.
|
160
|
+
out_log_ids = flox.find_log_ids(nil, limit)
|
161
161
|
assert_equal(limit, out_log_ids.length)
|
162
162
|
end
|
163
163
|
|
data/test/test_query.rb
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
## Author: Daniel Sperl
|
2
|
+
## Copyright: Copyright 2014 Gamua
|
3
|
+
## License: Simplified BSD
|
4
|
+
|
5
|
+
require 'flox'
|
6
|
+
require 'test/unit'
|
7
|
+
require 'mocha/setup'
|
8
|
+
|
9
|
+
class FloxTest < Test::Unit::TestCase
|
10
|
+
|
11
|
+
def test_constraints
|
12
|
+
time_xs = "2014-03-10T14:00:00.124Z"
|
13
|
+
time = Time.parse(time_xs)
|
14
|
+
query = Flox::Query.new("Type")
|
15
|
+
query.where "count >= ? AND (name == ? OR date <= ?)", 10, "hugo", time
|
16
|
+
assert_equal("count >= 10 AND (name == \"hugo\" OR date <= \"#{time_xs}\")",
|
17
|
+
query.constraints, "wrong placeholder replacement")
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_constraints_with_array
|
21
|
+
query = Flox::Query.new("Type")
|
22
|
+
query.where "name IN ?", %w{ alpha bravo charlie }
|
23
|
+
assert_equal('name IN ["alpha","bravo","charlie"]', query.constraints)
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_offset
|
27
|
+
query = Flox::Query.new("Type")
|
28
|
+
query.offset = 15
|
29
|
+
assert_equal(15, query.offset)
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_limit
|
33
|
+
query = Flox::Query.new("Type")
|
34
|
+
query.limit = 5
|
35
|
+
assert_equal(5, query.limit)
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_constraints_checks_argument_count
|
39
|
+
query = Flox::Query.new("Type")
|
40
|
+
assert_raise ArgumentError do
|
41
|
+
query.where "a == ? AND b == ?", 10
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def test_run_query
|
46
|
+
flox = Flox.new("game_id", "game_key")
|
47
|
+
query = Flox::Query.new("Type", "score > ?", 100)
|
48
|
+
flox.service.expects(:request).times(3).returns([{id: 1}, {id: 2}])
|
49
|
+
.then.returns({score: 101}, {score: 102})
|
50
|
+
results = flox.find_entities query
|
51
|
+
assert_equal 2, results.length
|
52
|
+
|
53
|
+
result_0 = results[0]
|
54
|
+
assert_kind_of(Flox::Entity, result_0)
|
55
|
+
assert_equal(101, result_0[:score])
|
56
|
+
|
57
|
+
result_1 = results[1]
|
58
|
+
assert_kind_of(Flox::Entity, result_1)
|
59
|
+
assert_equal(102, result_1[:score])
|
60
|
+
end
|
61
|
+
|
62
|
+
def test_run_query_direct
|
63
|
+
flox = Flox.new("game_id", "game_key")
|
64
|
+
flox.service.expects(:request).times(3).returns([{id: 1}, {id: 2}])
|
65
|
+
.then.returns({}, {})
|
66
|
+
results = flox.find_entities "Type", "score > ?", 100
|
67
|
+
assert_equal 2, results.length
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: flox
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Daniel Sperl
|
@@ -97,10 +97,12 @@ files:
|
|
97
97
|
- bin/flox
|
98
98
|
- test/test_entity.rb
|
99
99
|
- test/test_flox.rb
|
100
|
+
- test/test_query.rb
|
100
101
|
- test/test_utils.rb
|
101
102
|
- lib/flox/entity.rb
|
103
|
+
- lib/flox/errors.rb
|
102
104
|
- lib/flox/player.rb
|
103
|
-
- lib/flox/
|
105
|
+
- lib/flox/query.rb
|
104
106
|
- lib/flox/rest_service.rb
|
105
107
|
- lib/flox/score.rb
|
106
108
|
- lib/flox/utils.rb
|
@@ -1,43 +0,0 @@
|
|
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
|