i18n-backend-http 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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