i18n-backend-http 0.2.0 → 0.3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 6253fa4b449db0c86fc538654b5d435d1cbcf0a7
4
- data.tar.gz: 7672f4606b3645647594312efe3e3c1f4f93bfc3
3
+ metadata.gz: c8ef7e0cc698935821cf0bc559a97a73e29185c4
4
+ data.tar.gz: df9271b1c5f1f2301061695042ed6eaa474c3900
5
5
  SHA512:
6
- metadata.gz: f98a33068b3b8766b80d6c32c4939ed2546b7ebf9fd3b0f4d9f4ff47821b80ea089a7960ce645870f233e49f404f2131f0085441d5b87dd21477e4b16bee1ebd
7
- data.tar.gz: b80e56c5688166d236e6f941000a23f958dd27b768fc7cd600e4772c83bffe7676dc250a91c93012dee400cb455e5a8b5cfbfc7d5e82ede0e892a5e5e2e85843
6
+ metadata.gz: b420d096c879cca888b43130e643b34c0e21b05eca68f7c84eca4fa873a67ad611490703d5d300a8a0bbab6ef72831f449d6bfee92f64d3624526c1f2640ca49
7
+ data.tar.gz: 859c684dd33e4dee4828051e65664fceca3a826cc25a867fc8101df01d792b2ec558091e9731f897d6cbed0f60ff4af72017e2bfe3faa505ee37da6814e80f81
@@ -3,7 +3,6 @@ require 'i18n/backend/transliterator'
3
3
  require 'i18n/backend/base'
4
4
  require 'i18n/backend/http/version'
5
5
  require 'i18n/backend/http/etag_http_client'
6
- require 'i18n/backend/http/null_cache'
7
6
  require 'i18n/backend/http/lru_cache'
8
7
  require 'socket'
9
8
 
@@ -11,16 +10,17 @@ module I18n
11
10
  module Backend
12
11
  class Http
13
12
  include ::I18n::Backend::Base
13
+ FAILED_GET = {}.freeze
14
14
 
15
15
  def initialize(options)
16
16
  @options = {
17
- :http_open_timeout => 1,
18
- :http_read_timeout => 1,
19
- :polling_interval => 10*60,
20
- :cache => NullCache.new,
21
- :poll => true,
22
- :exception_handler => lambda{|e| $stderr.puts e },
23
- :memory_cache_size => 10,
17
+ http_open_timeout: 1,
18
+ http_read_timeout: 1,
19
+ polling_interval: 10*60,
20
+ cache: nil,
21
+ poll: true,
22
+ exception_handler: -> (e) { $stderr.puts e },
23
+ memory_cache_size: 10,
24
24
  }.merge(options)
25
25
 
26
26
  @http_client = EtagHttpClient.new(@options)
@@ -41,7 +41,7 @@ module I18n
41
41
  def start_polling
42
42
  Thread.new do
43
43
  until @stop_polling
44
- sleep(@options[:polling_interval])
44
+ sleep(@options.fetch(:polling_interval))
45
45
  update_caches
46
46
  end
47
47
  end
@@ -53,69 +53,62 @@ module I18n
53
53
  end
54
54
 
55
55
  def translations(locale)
56
- @translations[locale] ||= (
57
- translations_from_cache(locale) ||
58
- download_and_cache_translations(locale)
59
- )
56
+ (@translations[locale] ||= fetch_and_update_cached_translations(locale, nil, update: false)).first
60
57
  end
61
58
 
62
- def update_caches
63
- @translations.keys.each do |locale|
64
- if @options[:cache].is_a?(NullCache)
65
- download_and_cache_translations(locale)
66
- else
67
- locked_update_cache(locale)
68
- end
69
- end
70
- end
71
-
72
- def locked_update_cache(locale)
73
- unless update_cache(locale) { download_and_cache_translations(locale) }
74
- update_memory_cache_from_cache(locale)
75
- end
76
- end
59
+ def fetch_and_update_cached_translations(locale, old_etag, update:)
60
+ if cache = @options.fetch(:cache)
61
+ key = cache_key(locale)
62
+ interval = @options.fetch(:polling_interval)
63
+ now = Time.now # capture time before we do slow work to stay on schedule
64
+ old_value, old_etag, expires_at = cache.read(key) # assumes the cache is more recent then our local storage
77
65
 
78
- def update_cache(locale)
79
- cache = @options.fetch(:cache)
80
- key = "i18n/backend/http/locked_update_caches/#{locale}"
81
- me = "#{Socket.gethostname}-#{Process.pid}-#{Thread.current.object_id}"
82
- if current = cache.read(key)
83
- if current == me
84
- try = false # I am responsible, renew expiration
85
- else
86
- return # someone else is responsible, do not touch
66
+ if old_value && (!update || expires_at > now || !updater?(cache, key, interval))
67
+ return [old_value, old_etag]
87
68
  end
69
+
70
+ new_value, new_etag = download_translations(locale, etag: old_etag)
71
+ new_expires_at = now + interval
72
+ cache.write(key, [new_value, new_etag, new_expires_at])
73
+ [new_value, new_etag]
88
74
  else
89
- try = true # nobody is responsible, try to get responsibility
75
+ download_translations(locale, etag: old_etag)
90
76
  end
91
-
92
- return unless cache.write(key, me, expires_in: (@options[:polling_interval] * 3).ceil, unless_exist: try)
93
-
94
- yield
95
- true
96
77
  end
97
78
 
98
- def update_memory_cache_from_cache(locale)
99
- @translations[locale] = translations_from_cache(locale)
79
+ # sync with the cache who is going to update the cache
80
+ # this overlaps with the expiration interval, so worst case we will get 2x the interval
81
+ # if all servers are in sync and check updater at the same time
82
+ def updater?(cache, key, interval)
83
+ cache.write(
84
+ "#{key}-lock",
85
+ true,
86
+ expires_in: interval,
87
+ unless_exist: true
88
+ )
100
89
  end
101
90
 
102
- def translations_from_cache(locale)
103
- @options[:cache].read(cache_key(locale))
91
+ # when download fails we keep our old caches since they are most likely better then nothing
92
+ def update_caches
93
+ @translations.keys.each do |locale|
94
+ _, old_etag = @translations[locale]
95
+ result = fetch_and_update_cached_translations(locale, old_etag, update: true)
96
+ if result && result.first != self.class::FAILED_GET
97
+ @translations[locale] = result
98
+ end
99
+ end
104
100
  end
105
101
 
106
102
  def cache_key(locale)
107
- "i18n/backend/http/translations/#{locale}"
103
+ "i18n/backend/http/translations/#{locale}/v2"
108
104
  end
109
105
 
110
- def download_and_cache_translations(locale)
111
- @http_client.download(path(locale)) do |result|
112
- translations = parse_response(result)
113
- @options[:cache].write(cache_key(locale), translations)
114
- @translations[locale] = translations
115
- end
106
+ def download_translations(locale, etag:)
107
+ result, etag = @http_client.download(path(locale), etag: etag)
108
+ [parse_response(result), etag] if result
116
109
  rescue => e
117
- @options[:exception_handler].call(e)
118
- @translations[locale] = {} # do not write distributed cache
110
+ @options.fetch(:exception_handler).call(e)
111
+ [self.class::FAILED_GET, nil]
119
112
  end
120
113
 
121
114
  def parse_response(body)
@@ -6,22 +6,19 @@ module I18n
6
6
  class I18n::Backend::Http::EtagHttpClient
7
7
  def initialize(options)
8
8
  @options = options
9
- @etags = {}
10
9
  end
11
10
 
12
- def download(path)
11
+ def download(path, etag:)
13
12
  @client ||= Faraday.new(@options[:host])
14
13
  response = @client.get(path) do |request|
15
- request.headers["If-None-Match"] = @etags[path] if @etags[path]
14
+ request.headers["If-None-Match"] = etag if etag
16
15
  request.options[:timeout] = @options[:http_read_timeout]
17
16
  request.options[:open_timeout] = @options[:http_open_timeout]
18
17
  end
19
18
 
20
- @etags[path] = response['ETag']
21
-
22
19
  case response.status
23
- when 200 then yield response.body
24
- when 304
20
+ when 200 then [response.body, response['ETag']]
21
+ when 304 then nil
25
22
  else
26
23
  raise "Failed request: #{response.inspect}"
27
24
  end
@@ -1,7 +1,7 @@
1
1
  module I18n
2
2
  module Backend
3
3
  class Http
4
- VERSION = Version = "0.2.0"
4
+ VERSION = Version = "0.3.0"
5
5
  end
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: i18n-backend-http
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michael Grosser
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-01-10 00:00:00.000000000 Z
11
+ date: 2017-01-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: i18n
@@ -173,7 +173,6 @@ files:
173
173
  - lib/i18n/backend/http.rb
174
174
  - lib/i18n/backend/http/etag_http_client.rb
175
175
  - lib/i18n/backend/http/lru_cache.rb
176
- - lib/i18n/backend/http/null_cache.rb
177
176
  - lib/i18n/backend/http/version.rb
178
177
  homepage: https://github.com/grosser/i18n-backend-http
179
178
  licenses:
@@ -187,7 +186,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
187
186
  requirements:
188
187
  - - ">="
189
188
  - !ruby/object:Gem::Version
190
- version: 2.0.0
189
+ version: 2.1.0
191
190
  required_rubygems_version: !ruby/object:Gem::Requirement
192
191
  requirements:
193
192
  - - ">="
@@ -1,18 +0,0 @@
1
- module I18n
2
- module Backend
3
- class Http
4
- class I18n::Backend::Http::NullCache
5
- def fetch(*args)
6
- yield
7
- end
8
-
9
- def read(key)
10
- end
11
-
12
- def write(key, value)
13
- value
14
- end
15
- end
16
- end
17
- end
18
- end