4me-sdk 1.1.4 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/4me-sdk.gemspec +1 -1
- data/Gemfile.lock +15 -17
- data/LICENSE +1 -1
- data/README.md +45 -27
- data/lib/sdk4me.rb +4 -3
- data/lib/sdk4me/client.rb +52 -20
- data/lib/sdk4me/client/attachments.rb +52 -9
- data/lib/sdk4me/client/response.rb +9 -0
- data/lib/sdk4me/client/version.rb +1 -1
- data/spec/lib/sdk4me/attachments_spec.rb +230 -145
- data/spec/lib/sdk4me/certificate_spec.rb +15 -1
- data/spec/lib/sdk4me/client_spec.rb +480 -441
- data/spec/lib/sdk4me/response_spec.rb +251 -231
- data/spec/lib/sdk4me_spec.rb +4 -4
- metadata +8 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: dc85cfa0c6b08a248805e24e37b4fe0cde152c64d42443b72ca8db4e5cf2799a
|
4
|
+
data.tar.gz: ebfe8f4d1e32e791ef5d109ef6934aa3fa585a60df8cd1f4f000073993cc9a2f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e05769acff36f8c0ffc6758d4078217b4af13cd969814ee85699d0e84951c6b9d34d6d4b2d7fb0fddb2577a2a03b4aeda3b7c687d0ec62916ef8e712ce5224cc
|
7
|
+
data.tar.gz: 6cac609bb11b34d97086e701701d7f664ca24500cca943a948e7ce42d7650e2dd2c8f14a0a348dd6d0b2295c90257d1a39411ae0f2e7a8a67202396edf8dc31b
|
data/4me-sdk.gemspec
CHANGED
@@ -34,7 +34,7 @@ Gem::Specification.new do |spec|
|
|
34
34
|
spec.add_development_dependency 'bundler', '~> 1'
|
35
35
|
spec.add_development_dependency 'rake', '~> 12'
|
36
36
|
spec.add_development_dependency 'rspec', '~> 3.3'
|
37
|
-
spec.add_development_dependency 'webmock', '~>
|
37
|
+
spec.add_development_dependency 'webmock', '~> 3'
|
38
38
|
spec.add_development_dependency 'simplecov', '~> 0'
|
39
39
|
|
40
40
|
end
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
4me-sdk (1.
|
4
|
+
4me-sdk (1.2.0)
|
5
5
|
activesupport (>= 4.2)
|
6
6
|
gem_config (>= 0.3)
|
7
7
|
mime-types (>= 3.0)
|
@@ -9,28 +9,27 @@ PATH
|
|
9
9
|
GEM
|
10
10
|
remote: https://rubygems.org/
|
11
11
|
specs:
|
12
|
-
activesupport (5.2.
|
12
|
+
activesupport (5.2.2)
|
13
13
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
14
14
|
i18n (>= 0.7, < 2)
|
15
15
|
minitest (~> 5.1)
|
16
16
|
tzinfo (~> 1.1)
|
17
|
-
addressable (2.
|
18
|
-
public_suffix (>= 2.0.2, <
|
19
|
-
concurrent-ruby (1.1.
|
20
|
-
crack (0.4.
|
21
|
-
safe_yaml (~> 1.0.0)
|
17
|
+
addressable (2.7.0)
|
18
|
+
public_suffix (>= 2.0.2, < 5.0)
|
19
|
+
concurrent-ruby (1.1.5)
|
20
|
+
crack (0.4.4)
|
22
21
|
diff-lcs (1.3)
|
23
22
|
docile (1.3.1)
|
24
23
|
gem_config (0.3.1)
|
25
|
-
hashdiff (0.
|
26
|
-
i18n (1.
|
24
|
+
hashdiff (1.0.1)
|
25
|
+
i18n (1.5.1)
|
27
26
|
concurrent-ruby (~> 1.0)
|
28
27
|
json (2.1.0)
|
29
28
|
mime-types (3.2.2)
|
30
29
|
mime-types-data (~> 3.2015)
|
31
|
-
mime-types-data (3.
|
32
|
-
minitest (5.
|
33
|
-
public_suffix (
|
30
|
+
mime-types-data (3.2019.0331)
|
31
|
+
minitest (5.13.0)
|
32
|
+
public_suffix (4.0.6)
|
34
33
|
rake (12.3.1)
|
35
34
|
rspec (3.8.0)
|
36
35
|
rspec-core (~> 3.8.0)
|
@@ -45,7 +44,6 @@ GEM
|
|
45
44
|
diff-lcs (>= 1.2.0, < 2.0)
|
46
45
|
rspec-support (~> 3.8.0)
|
47
46
|
rspec-support (3.8.0)
|
48
|
-
safe_yaml (1.0.4)
|
49
47
|
simplecov (0.16.1)
|
50
48
|
docile (~> 1.1)
|
51
49
|
json (>= 1.8, < 3)
|
@@ -54,10 +52,10 @@ GEM
|
|
54
52
|
thread_safe (0.3.6)
|
55
53
|
tzinfo (1.2.5)
|
56
54
|
thread_safe (~> 0.1)
|
57
|
-
webmock (
|
55
|
+
webmock (3.9.1)
|
58
56
|
addressable (>= 2.3.6)
|
59
57
|
crack (>= 0.3.2)
|
60
|
-
hashdiff
|
58
|
+
hashdiff (>= 0.4.0, < 2.0.0)
|
61
59
|
|
62
60
|
PLATFORMS
|
63
61
|
ruby
|
@@ -68,7 +66,7 @@ DEPENDENCIES
|
|
68
66
|
rake (~> 12)
|
69
67
|
rspec (~> 3.3)
|
70
68
|
simplecov (~> 0)
|
71
|
-
webmock (~>
|
69
|
+
webmock (~> 3)
|
72
70
|
|
73
71
|
BUNDLED WITH
|
74
|
-
1.
|
72
|
+
1.17.3
|
data/LICENSE
CHANGED
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# Sdk4me::Client
|
2
2
|
|
3
|
-
Client for accessing the [4me REST API](
|
3
|
+
Client for accessing the [4me REST API](https://developer.4me.com/v1/)
|
4
4
|
|
5
5
|
## Installation
|
6
6
|
|
@@ -22,7 +22,7 @@ Or install it yourself as:
|
|
22
22
|
|
23
23
|
```
|
24
24
|
Sdk4me.configure do |config|
|
25
|
-
config.
|
25
|
+
config.access_token = 'd41f5868feb65fc87fa2311a473a8766ea38bc40'
|
26
26
|
config.account = 'my-sandbox'
|
27
27
|
config.logger = Rails.logger
|
28
28
|
...
|
@@ -32,17 +32,20 @@ end
|
|
32
32
|
All options available:
|
33
33
|
|
34
34
|
* _logger_: The [Ruby Logger](http://www.ruby-doc.org/stdlib-1.9.3/libdoc/logger/rdoc/Logger.html) instance, default: `Logger.new(STDOUT)`
|
35
|
-
* _host_: The [4me API host](
|
36
|
-
* _api_version_: The [4me API version](
|
37
|
-
*
|
38
|
-
*
|
39
|
-
*
|
40
|
-
*
|
35
|
+
* _host_: The [4me API host](https://developer.4me.com/v1/#service-url), default: 'https://api.4me.com'
|
36
|
+
* _api_version_: The [4me API version](https://developer.4me.com/v1/#service-url), default: 'v1'
|
37
|
+
* _access_token_: (**required**) The [4me access token](https://developer.4me.com/v1/#authentication)
|
38
|
+
* _api_token_: (**deprecated**) The [4me API token](https://developer.4me.com/v1/#api-tokens)
|
39
|
+
* _account_: Specify a [different account](https://developer.4me.com/v1/#multiple-accounts) to work with
|
40
|
+
* _source_: The [source](https://developer.4me.com/v1/general/source/) used when creating new records
|
41
|
+
* _max_retry_time_: maximum nr of seconds to retry a request on a failed response (default = 300 = 5 minutes)<br/>
|
41
42
|
The sleep time between retries starts at 2 seconds and doubles after each retry, i.e.
|
42
|
-
2, 6, 18, 54, 162, 486, 1458,
|
43
|
-
|
43
|
+
2, 6, 18, 54, 162, 486, 1458, ... seconds.<br/>
|
44
|
+
Set to 0 to prevent retries.
|
44
45
|
* _read_timeout_: [HTTP read timeout](http://ruby-doc.org/stdlib-2.0.0/libdoc/net/http/rdoc/Net/HTTP.html#method-i-read_timeout-3D) in seconds (default = 25)
|
45
|
-
* _block_at_rate_limit_: Set to `true` to block the request until the [rate limit](
|
46
|
+
* _block_at_rate_limit_: Set to `true` to block the request until the [rate limit](https://developer.4me.com/v1/#rate-limiting) is lifted, default: `true`<br/>
|
47
|
+
The `Retry-After` header is used to compute when the retry should be performed. If that moment is later than the _max_throttle_time_ the request will not be blocked and the throttled response is returned.
|
48
|
+
* _max_throttle_time_: maximum nr of seconds to retry a request on a rate limiter (default = 3660 = 1 hour and 1 minute)<br/>
|
46
49
|
* _proxy_host_: Define in case HTTP traffic needs to go through a proxy
|
47
50
|
* _proxy_port_: Port of the proxy, defaults to 8080
|
48
51
|
* _proxy_user_: Proxy user
|
@@ -68,7 +71,7 @@ Minimal example:
|
|
68
71
|
```
|
69
72
|
require 'sdk4me/client'
|
70
73
|
|
71
|
-
client = Sdk4me::Client.new(
|
74
|
+
client = Sdk4me::Client.new(access_token: '3a4e4590179263839...')
|
72
75
|
response = client.get('me')
|
73
76
|
puts response[:primary_email]
|
74
77
|
```
|
@@ -82,7 +85,7 @@ response = Sdk4me::Client.new.get('organizations/4321')
|
|
82
85
|
puts response[:name]
|
83
86
|
```
|
84
87
|
|
85
|
-
By default this call will return all [fields](
|
88
|
+
By default this call will return all [fields](https://developer.4me.com/v1/organizations/#fields) of the Organization.
|
86
89
|
|
87
90
|
The fields can be accessed using *symbols* and *strings*, and it is possible chain a number of keys in one go:
|
88
91
|
```
|
@@ -103,8 +106,8 @@ end
|
|
103
106
|
puts "Found #{count} organizations"
|
104
107
|
```
|
105
108
|
|
106
|
-
By default this call will return all [collection fields](
|
107
|
-
For more fields, check out the [field selection](
|
109
|
+
By default this call will return all [collection fields](https://developer.4me.com/v1/organizations/#collection-fields) for each Organization.
|
110
|
+
For more fields, check out the [field selection](https://developer.4me.com/v1/general/field_selection/#collection-of-resources) documentation.
|
108
111
|
|
109
112
|
The fields can be accessed using *symbols* and *strings*, and it is possible chain a number of keys in one go:
|
110
113
|
```
|
@@ -121,7 +124,7 @@ Note that an `Sdk4me::Exception` could be thrown in case one of the API requests
|
|
121
124
|
|
122
125
|
The `each` method [described above](#browse-through-a-collection-of-records) is the preferred way to work with collections of data.
|
123
126
|
|
124
|
-
If you really want to [paginate](
|
127
|
+
If you really want to [paginate](https://developer.4me.com/v1/general/pagination/) yourself, the `get` method is your friend.
|
125
128
|
|
126
129
|
```
|
127
130
|
@client = Sdk4me::Client.new
|
@@ -139,8 +142,8 @@ next_page = @client.get(response.pagination_link(:next))
|
|
139
142
|
last_page = @client.get(response.pagination_link(:last))
|
140
143
|
```
|
141
144
|
|
142
|
-
By default this call will return all [collection fields](
|
143
|
-
For more fields, check out the [field selection](
|
145
|
+
By default this call will return all [collection fields](https://developer.4me.com/v1/organizations/#collection-fields) for each Organization.
|
146
|
+
For more fields, check out the [field selection](https://developer.4me.com/v1/general/field_selection/#collection-of-resources) documentation.
|
144
147
|
|
145
148
|
The fields can be accessed using *symbols* and *strings*, and it is possible chain a number of keys in one go:
|
146
149
|
```
|
@@ -211,6 +214,14 @@ response = Sdk4me::Client.new.put('requests/416621', {
|
|
211
214
|
})
|
212
215
|
```
|
213
216
|
|
217
|
+
It is also possible to add inline attachments as follows:
|
218
|
+
```
|
219
|
+
response = Sdk4me::Client.new.put('requests/416621', {
|
220
|
+
note: 'Here is some inspiration for you: [attachment:/tmp/images/puppy.png]'
|
221
|
+
})
|
222
|
+
```
|
223
|
+
Note that only images are accepted as inline attachments.
|
224
|
+
|
214
225
|
If an attachment upload fails, the errors are logged but the `post` or `put` request will still be sent to 4me without the
|
215
226
|
failed attachments. To receive exceptions add `attachments_exception: true` to the data.
|
216
227
|
|
@@ -233,7 +244,7 @@ end
|
|
233
244
|
|
234
245
|
### Importing CSV files
|
235
246
|
|
236
|
-
4me also provides an [Import API](
|
247
|
+
4me also provides an [Import API](https://developer.4me.com/v1/import/). The 4me SDK Client can be used to upload files to that API.
|
237
248
|
|
238
249
|
```
|
239
250
|
response = Sdk4me::Client.new.import('\tmp\people.csv', 'people')
|
@@ -245,9 +256,9 @@ end
|
|
245
256
|
|
246
257
|
```
|
247
258
|
|
248
|
-
The second argument contains the [import type](
|
259
|
+
The second argument contains the [import type](https://developer.4me.com/v1/import/#parameters).
|
249
260
|
|
250
|
-
It is also possible to [monitor the progress](
|
261
|
+
It is also possible to [monitor the progress](https://developer.4me.com/v1/import/#import-progress) of the import and block until the import is complete. In that case you will need to add some exception handling to your code.
|
251
262
|
|
252
263
|
```
|
253
264
|
begin
|
@@ -267,7 +278,7 @@ Note that blocking for the import to finish is required when you import multiple
|
|
267
278
|
|
268
279
|
### Exporting CSV files
|
269
280
|
|
270
|
-
4me also provides an [Export API](
|
281
|
+
4me also provides an [Export API](https://developer.4me.com/v1/export/). The 4me SDK Client can be used to download (zipped) CSV files using that API.
|
271
282
|
|
272
283
|
```
|
273
284
|
response = Sdk4me::Client.new.export(['people', 'people_contact_details'], DateTime.new(2012,03,30,23,00,00))
|
@@ -279,10 +290,10 @@ end
|
|
279
290
|
|
280
291
|
```
|
281
292
|
|
282
|
-
The first argument contains the [export types](
|
293
|
+
The first argument contains the [export types](https://developer.4me.com/v1/export/#parameters).
|
283
294
|
The second argument is optional and limits the export to all changed records since the given time.
|
284
295
|
|
285
|
-
It is also possible to [monitor the progress](
|
296
|
+
It is also possible to [monitor the progress](https://developer.4me.com/v1/export/#export-progress) of the export and block until the export is complete. In that case you will need to add some exception handling to your code.
|
286
297
|
|
287
298
|
```
|
288
299
|
require 'open-uri'
|
@@ -304,10 +315,17 @@ Note that blocking for the export to finish is recommended as you will get direc
|
|
304
315
|
|
305
316
|
### Blocking
|
306
317
|
|
307
|
-
|
318
|
+
When the currently used API token hits the [4me rate limiter](https://developer.4me.com/v1/#rate-limiting) a HTTP 429 response is returned that specifies after how many seconds the rate limit will be lifted.
|
319
|
+
|
320
|
+
If that time lies within the _max_throttle_time_ the 4me SDK Client will wait and retry the action, if not, the throttled response will be returned. You can verify if a response was throttled using:
|
321
|
+
```
|
322
|
+
response = client.get('me')
|
323
|
+
puts response.throttled?
|
324
|
+
```
|
308
325
|
|
309
|
-
By setting the _block_at_rate_limit_ to `
|
326
|
+
By setting the _block_at_rate_limit_ to `false` in the [configuration](#configuration) the 4me SDK Client will never wait when a rate limit is hit.
|
310
327
|
|
328
|
+
Note that 4me has different rate limiters. If the intention is to only wait when the short-burst (max 20 requests in 2 seconds) rate limiter is hit, you could set the _max_throttle_time_ to e.g. 5 seconds.
|
311
329
|
|
312
330
|
### Translations
|
313
331
|
|
@@ -319,7 +337,7 @@ When exporting translations, the _locale_ parameter is required:
|
|
319
337
|
|
320
338
|
### Exception handling
|
321
339
|
|
322
|
-
The standard methods `get`, `post`, `put` and `delete` will always return a Response with an [error message](
|
340
|
+
The standard methods `get`, `post`, `put` and `delete` will always return a Response with an [error message](https://developer.4me.com/v1/#http-status-codes) in case something went wrong.
|
323
341
|
|
324
342
|
By calling `response.valid?` you will know if the action succeeded or not, and `response.message` provides additinal information in case the response was invalid.
|
325
343
|
|
data/lib/sdk4me.rb
CHANGED
@@ -9,15 +9,16 @@ module Sdk4me
|
|
9
9
|
|
10
10
|
has :host, classes: String, default: 'https://api.4me.com'
|
11
11
|
has :api_version, values: ['v1'], default: 'v1'
|
12
|
+
has :access_token, classes: String
|
12
13
|
has :api_token, classes: String
|
13
14
|
|
14
15
|
has :account, classes: String
|
15
16
|
has :source, classes: String
|
16
17
|
|
17
|
-
has :max_retry_time, classes: Integer, default:
|
18
|
+
has :max_retry_time, classes: Integer, default: 300
|
18
19
|
has :read_timeout, classes: Integer, default: 25
|
19
|
-
has :block_at_rate_limit, classes: [TrueClass, FalseClass], default:
|
20
|
-
|
20
|
+
has :block_at_rate_limit, classes: [TrueClass, FalseClass], default: true
|
21
|
+
has :max_throttle_time, classes: Integer, default: 3660
|
21
22
|
has :proxy_host, classes: String
|
22
23
|
has :proxy_port, classes: Integer, default: 8080
|
23
24
|
has :proxy_user, classes: String
|
data/lib/sdk4me/client.rb
CHANGED
@@ -27,7 +27,7 @@ module Sdk4me
|
|
27
27
|
#
|
28
28
|
# Shared configuration for all 4me SDK Clients:
|
29
29
|
# Sdk4me.configure do |config|
|
30
|
-
# config.
|
30
|
+
# config.access_token = 'd41f5868feb65fc87fa2311a473a8766ea38bc40'
|
31
31
|
# config.account = 'my-sandbox'
|
32
32
|
# ...
|
33
33
|
# end
|
@@ -39,11 +39,11 @@ module Sdk4me
|
|
39
39
|
# - logger: The Ruby Logger instance, default: Logger.new(STDOUT)
|
40
40
|
# - host: The 4me API host, default: 'https://api.4me.com'
|
41
41
|
# - api_version: The 4me API version, default: 'v1'
|
42
|
-
# -
|
42
|
+
# - access_token: *required* The 4me access token
|
43
43
|
# - account: Specify a different (trusted) account to work with
|
44
|
-
# @see
|
44
|
+
# @see https://developer.4me.com/v1/#multiple-accounts
|
45
45
|
# - source: The Source used when creating new records
|
46
|
-
# @see
|
46
|
+
# @see https://developer.4me.com/v1/general/source/
|
47
47
|
#
|
48
48
|
# - max_retry_time: maximum nr of seconds to wait for server to respond (default = 5400 = 1.5 hours)
|
49
49
|
# the sleep time between retries starts at 2 seconds and doubles after each retry
|
@@ -51,7 +51,7 @@ module Sdk4me
|
|
51
51
|
# one retry will always be performed unless you set the value to -1
|
52
52
|
# - read_timeout: HTTP GET read timeout in seconds (default = 25)
|
53
53
|
# - block_at_rate_limit: Set to +true+ to block the request until the rate limit is lifted, default: +false+
|
54
|
-
# @see
|
54
|
+
# @see https://developer.4me.com/v1/#rate-limiting
|
55
55
|
#
|
56
56
|
# - proxy_host: Define in case HTTP traffic needs to go through a proxy
|
57
57
|
# - proxy_port: Port of the proxy, defaults to 8080
|
@@ -59,12 +59,19 @@ module Sdk4me
|
|
59
59
|
# - proxy_password: Proxy password
|
60
60
|
def initialize(options = {})
|
61
61
|
@options = Sdk4me.configuration.current.merge(options)
|
62
|
-
[:host, :api_version
|
62
|
+
[:host, :api_version].each do |required_option|
|
63
63
|
raise ::Sdk4me::Exception.new("Missing required configuration option #{required_option}") if option(required_option).blank?
|
64
64
|
end
|
65
|
+
@logger = @options[:logger]
|
65
66
|
@ssl, @domain, @port = ssl_domain_port_path(option(:host))
|
67
|
+
unless option(:access_token).present?
|
68
|
+
if option(:api_token).blank?
|
69
|
+
raise ::Sdk4me::Exception.new("Missing required configuration option access_token")
|
70
|
+
else
|
71
|
+
@logger.info('Use of api_token is deprecated, consider switching to access_token instead.')
|
72
|
+
end
|
73
|
+
end
|
66
74
|
@ssl_verify_none = options[:ssl_verify_none]
|
67
|
-
@logger = @options[:logger]
|
68
75
|
end
|
69
76
|
|
70
77
|
# Retrieve an option
|
@@ -211,7 +218,12 @@ module Sdk4me
|
|
211
218
|
def expand_header(header = {})
|
212
219
|
header = DEFAULT_HEADER.merge(header)
|
213
220
|
header['X-4me-Account'] = option(:account) if option(:account)
|
214
|
-
|
221
|
+
if option(:access_token).present?
|
222
|
+
header['AUTHORIZATION'] = 'Bearer ' + option(:access_token)
|
223
|
+
else
|
224
|
+
token_and_password = option(:api_token).include?(':') ? option(:api_token) : "#{option(:api_token)}:x"
|
225
|
+
header['AUTHORIZATION'] = 'Basic ' + [token_and_password].pack('m*').gsub(/\s/, '')
|
226
|
+
end
|
215
227
|
if option(:source)
|
216
228
|
header['X-4me-Source'] = option(:source)
|
217
229
|
header['HTTP_USER_AGENT'] = option(:source)
|
@@ -283,7 +295,7 @@ module Sdk4me
|
|
283
295
|
elsif '303' == response.raw.code.to_s
|
284
296
|
@logger.debug { "Redirect: #{response.raw.header['Location']}" }
|
285
297
|
else
|
286
|
-
@logger.error { "
|
298
|
+
@logger.error { "#{request.method} request to #{domain}:#{port}#{request.path} failed: #{response.message}" }
|
287
299
|
end
|
288
300
|
response
|
289
301
|
end
|
@@ -298,14 +310,25 @@ module Sdk4me
|
|
298
310
|
end
|
299
311
|
|
300
312
|
module SendWithRateLimitBlock
|
301
|
-
# Wraps the _send method with retries when the server does not
|
313
|
+
# Wraps the _send method with retries when the server does not respond, see +initialize+ option +:rate_limit_block+
|
302
314
|
def _send(request, domain = @domain, port = @port, ssl = @ssl)
|
303
|
-
return super(request, domain, port, ssl) unless option(:block_at_rate_limit)
|
304
|
-
now =
|
315
|
+
return super(request, domain, port, ssl) unless option(:block_at_rate_limit) && option(:max_throttle_time) > 0
|
316
|
+
now = nil
|
317
|
+
timed_out = false
|
305
318
|
begin
|
306
319
|
_response = super(request, domain, port, ssl)
|
307
|
-
|
308
|
-
|
320
|
+
now ||= Time.now
|
321
|
+
if _response.throttled?
|
322
|
+
# if no Retry-After is not provided, the 4me server is very busy, wait 5 minutes
|
323
|
+
retry_after = _response.retry_after == 0 ? 300 : [_response.retry_after, 2].max
|
324
|
+
if (Time.now - now + retry_after) < option(:max_throttle_time)
|
325
|
+
@logger.warn { "Request throttled, trying again in #{retry_after} seconds: #{_response.message}" }
|
326
|
+
sleep(retry_after)
|
327
|
+
else
|
328
|
+
timed_out = true
|
329
|
+
end
|
330
|
+
end
|
331
|
+
end while _response.throttled? && !timed_out
|
309
332
|
_response
|
310
333
|
end
|
311
334
|
end
|
@@ -314,15 +337,24 @@ module Sdk4me
|
|
314
337
|
module SendWithRetries
|
315
338
|
# Wraps the _send method with retries when the server does not respond, see +initialize+ option +:retries+
|
316
339
|
def _send(request, domain = @domain, port = @port, ssl = @ssl)
|
340
|
+
return super(request, domain, port, ssl) unless option(:max_retry_time) > 0
|
317
341
|
retries = 0
|
318
|
-
sleep_time =
|
319
|
-
|
342
|
+
sleep_time = 1
|
343
|
+
now = nil
|
344
|
+
timed_out = false
|
320
345
|
begin
|
321
346
|
_response = super(request, domain, port, ssl)
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
347
|
+
now ||= Time.now
|
348
|
+
if _response.failure?
|
349
|
+
sleep_time *= 2
|
350
|
+
if (Time.now - now + sleep_time) < option(:max_retry_time)
|
351
|
+
@logger.warn { "Request failed, retry ##{retries += 1} in #{sleep_time} seconds: #{_response.message}" }
|
352
|
+
sleep(sleep_time)
|
353
|
+
else
|
354
|
+
timed_out = true
|
355
|
+
end
|
356
|
+
end
|
357
|
+
end while _response.failure? && !timed_out
|
326
358
|
_response
|
327
359
|
end
|
328
360
|
end
|
@@ -8,21 +8,64 @@ module Sdk4me
|
|
8
8
|
@client = client
|
9
9
|
end
|
10
10
|
|
11
|
-
# upload the attachments
|
11
|
+
# upload the attachments and return the data with the uploaded attachment info
|
12
|
+
# Two flavours available
|
13
|
+
# * data[:attachments]
|
14
|
+
# * data[:note] containing text with '[attachment:/tmp/images/green_fuzz.jpg]'
|
12
15
|
def upload_attachments!(path, data)
|
13
|
-
|
16
|
+
upload_options = {
|
17
|
+
raise_exceptions: !!data.delete(:attachments_exception),
|
18
|
+
attachments_field: attachments_field(path),
|
19
|
+
}
|
20
|
+
uploaded_attachments = upload_normal_attachments!(path, data, upload_options)
|
21
|
+
uploaded_attachments += upload_inline_attachments!(path, data, upload_options)
|
22
|
+
# jsonify the attachments, if any were uploaded
|
23
|
+
data[upload_options[:attachments_field]] = uploaded_attachments.compact.to_json if uploaded_attachments.compact.any?
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
# upload the attachments in :attachments to 4me and return the data with the uploaded attachment info
|
29
|
+
def upload_normal_attachments!(path, data, upload_options)
|
14
30
|
attachments = [data.delete(:attachments)].flatten.compact
|
15
|
-
return if attachments.empty?
|
31
|
+
return [] if attachments.empty?
|
16
32
|
|
17
|
-
|
18
|
-
|
19
|
-
report_error("Attachments not allowed for #{path}", raise_exceptions) and return unless storage
|
33
|
+
upload_options[:storage] ||= storage(path, upload_options[:raise_exceptions])
|
34
|
+
return [] unless upload_options[:storage]
|
20
35
|
|
21
|
-
|
22
|
-
|
36
|
+
attachments.map do |attachment|
|
37
|
+
upload_attachment(upload_options[:storage], attachment, upload_options[:raise_exceptions])
|
38
|
+
end
|
23
39
|
end
|
24
40
|
|
25
|
-
|
41
|
+
INLINE_ATTACHMENT_REGEXP = /\[attachment:([^\]]+)\]/.freeze
|
42
|
+
# upload any '[attachment:/tmp/images/green_fuzz.jpg]' in :note text field to 4me as inline attachment and add the s3 key to the text
|
43
|
+
def upload_inline_attachments!(path, data, upload_options)
|
44
|
+
text_field = upload_options[:attachments_field].to_s.gsub('_attachments', '').to_sym
|
45
|
+
return [] unless (data[text_field] || '') =~ INLINE_ATTACHMENT_REGEXP
|
46
|
+
|
47
|
+
upload_options[:storage] ||= storage(path, upload_options[:raise_exceptions])
|
48
|
+
return [] unless upload_options[:storage]
|
49
|
+
|
50
|
+
attachments = []
|
51
|
+
data[text_field] = data[text_field].gsub(INLINE_ATTACHMENT_REGEXP) do |full_match|
|
52
|
+
attachment_details = upload_attachment(upload_options[:storage], $~[1], upload_options[:raise_exceptions])
|
53
|
+
if attachment_details
|
54
|
+
attachments << attachment_details.merge(inline: true)
|
55
|
+
"![](#{attachment_details[:key]})" # magic markdown for inline attachments
|
56
|
+
else
|
57
|
+
full_match
|
58
|
+
end
|
59
|
+
end
|
60
|
+
attachments
|
61
|
+
end
|
62
|
+
|
63
|
+
def storage(path, raise_exceptions)
|
64
|
+
# retrieve the upload configuration for this record from 4me
|
65
|
+
storage = @client.get(path =~ /\d+$/ ? path : "#{path}/new", {attachment_upload_token: true}, @client.send(:expand_header))[:storage_upload]
|
66
|
+
report_error("Attachments not allowed for #{path}", raise_exceptions) unless storage
|
67
|
+
storage
|
68
|
+
end
|
26
69
|
|
27
70
|
def attachments_field(path)
|
28
71
|
case path
|