4me-sdk 1.1.4 → 1.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 +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
|
+
"" # 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
|