flox 0.1.2 → 0.2.0
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 +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
|