device_detector 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: 3b1d7d2d1f51f584026667fa8fff86b9d4cf994d
4
- data.tar.gz: dc71e14d78d7432fd03ea583fbaaf039a1aa3242
3
+ metadata.gz: 3831c0674436e07a0133b2396d2662d9e7c557e7
4
+ data.tar.gz: 913f71fc60c565076298fd8eb9a6c93184e048b0
5
5
  SHA512:
6
- metadata.gz: 956b2f1762edd6106dd96a8c920b88d0e1b323552790ba46d533063028802c4ca91f739a4c49d95a4ade4f273f1abad48a2a73781177ccdfd776857c45e087cc
7
- data.tar.gz: 49a4bbc2a254452296f155e11837772afb3a4399308a18eb035c98ab0f7ead38ab850f0650b802043c4701afe077788ebae5a1ae33d52e4a2d2d6926281f6de6
6
+ metadata.gz: 9f1fb95f6cfda05b85d7ae536c592f59098ba36075eff80ddbaca323505c4177023fdf9b879e3a2c769e085c25df5f61d5dababa019f1a15afc67e84d6f147d8
7
+ data.tar.gz: c29d2e75eaf37b83201b95ceeed6118157388c58e340c0d489beca60b0936250002e0b72759295b27d80229af900d3674d511208d1cf325fd2fdbed54bfa92f2
data/README.md CHANGED
@@ -48,6 +48,19 @@ client.os_full_version # => '10_8_5'
48
48
 
49
49
  `DeviceDetector` will return `nil` on all attributes, if the `user_agent` is unknown.
50
50
 
51
+ ### Memory cache
52
+
53
+ `DeviceDetector` will cache up 5,000 user agent strings to boost parsing performance.
54
+ You can tune the amount of keys that will get saved in the cache:
55
+
56
+ ```ruby
57
+
58
+ # You have to call this code **before** you initialize the Detector
59
+ DeviceDetector.configure do |config|
60
+ config.max_cache_keys = 20_000 # if you have enough RAM, proceed with care
61
+ end
62
+ ```
63
+
51
64
  ## Contributing
52
65
 
53
66
  1. Fork it ( https://github.com/[my-github-username]/device_detector/fork )
@@ -5,6 +5,7 @@ $LOAD_PATH.unshift(File.dirname(__FILE__))
5
5
 
6
6
  require 'device_detector/version'
7
7
  require 'device_detector/version_extractor'
8
+ require 'device_detector/memory_cache'
8
9
  require 'device_detector/parser'
9
10
  require 'device_detector/bot'
10
11
  require 'device_detector/client'
@@ -46,6 +47,33 @@ class DeviceDetector
46
47
  bot.name
47
48
  end
48
49
 
50
+ class << self
51
+
52
+ class Configuration
53
+ attr_accessor :max_cache_keys
54
+
55
+ def to_hash
56
+ {
57
+ max_cache_keys: max_cache_keys
58
+ }
59
+ end
60
+ end
61
+
62
+ def config
63
+ @config ||= Configuration.new
64
+ end
65
+
66
+ def cache
67
+ @cache ||= MemoryCache.new(config.to_hash)
68
+ end
69
+
70
+ def configure(&block)
71
+ @config = Configuration.new
72
+ yield(config)
73
+ end
74
+
75
+ end
76
+
49
77
  private
50
78
 
51
79
  def bot
@@ -0,0 +1,56 @@
1
+ class DeviceDetector
2
+ class MemoryCache
3
+
4
+ DEFAULT_MAX_KEYS = 5000
5
+
6
+ attr_reader :data, :max_keys
7
+
8
+ def initialize(config)
9
+ @data = {}
10
+ @max_keys = config[:max_cache_keys] || DEFAULT_MAX_KEYS
11
+ @lock = Mutex.new
12
+ end
13
+
14
+ def set(key, value)
15
+ lock.synchronize do
16
+ purge_cache
17
+ data[String(key)] = value
18
+ end
19
+ end
20
+
21
+ def get(key)
22
+ data[String(key)]
23
+ end
24
+
25
+ def key?(string_key)
26
+ data.key?(string_key)
27
+ end
28
+
29
+ def get_or_set(key, value = nil)
30
+ string_key = String(key)
31
+
32
+ if key?(string_key)
33
+ get(string_key)
34
+ else
35
+ value = yield if block_given?
36
+ set(string_key, value)
37
+ end
38
+ end
39
+
40
+ private
41
+
42
+ attr_reader :lock
43
+
44
+ def purge_cache
45
+ key_size = data.keys.size
46
+
47
+ if key_size >= max_keys
48
+ # always remove about 1/3 of keys to reduce garbage collecting
49
+ amount_of_keys = key_size / 3
50
+
51
+ data.keys.first(amount_of_keys).each { |key| data.delete(key) }
52
+ end
53
+ end
54
+
55
+ end
56
+ end
@@ -1,6 +1,8 @@
1
1
  class DeviceDetector
2
2
  class Parser < Struct.new(:user_agent)
3
3
 
4
+ ROOT = Pathname.new(File.expand_path('../../..', __FILE__))
5
+
4
6
  def name
5
7
  regex_meta['name']
6
8
  end
@@ -16,11 +18,13 @@ class DeviceDetector
16
18
  end
17
19
 
18
20
  def matching_regex
19
- regexes.find { |r| user_agent =~ Regexp.new(r['regex']) }
21
+ DeviceDetector.cache.get_or_set([self.class.name, user_agent]) do
22
+ regexes.find { |r| user_agent =~ Regexp.new(r['regex']) }
23
+ end
20
24
  end
21
25
 
22
26
  def regexes
23
- YAML.load(filepaths.map { |filepath| File.read(filepath) }.join)
27
+ self.class.regexes_for(filepaths)
24
28
  end
25
29
 
26
30
  def filenames
@@ -29,12 +33,15 @@ class DeviceDetector
29
33
 
30
34
  def filepaths
31
35
  filenames.map do |filename|
32
- File.join(root, 'regexes', filename)
36
+ File.join(ROOT, 'regexes', filename)
33
37
  end
34
38
  end
35
39
 
36
- def root
37
- Pathname.new(File.expand_path('../../..', __FILE__))
40
+ # This is a performance optimization.
41
+ # We cache the regexes on the class for better performance
42
+ # Thread-safety shouldn't be an issue, as we do only perform reads
43
+ def self.regexes_for(filepaths)
44
+ @regexes ||= YAML.load(filepaths.map { |filepath| File.read(filepath) }.join)
38
45
  end
39
46
 
40
47
  end
@@ -1,3 +1,3 @@
1
1
  class DeviceDetector
2
- VERSION = '0.2.0'
2
+ VERSION = '0.3.0'
3
3
  end
@@ -0,0 +1,116 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe DeviceDetector::MemoryCache do
4
+
5
+ let(:subject) { DeviceDetector::MemoryCache.new(config) }
6
+
7
+ let(:config) { {} }
8
+
9
+ describe '#set' do
10
+
11
+ context 'string key' do
12
+
13
+ let(:key) { 'string' }
14
+
15
+ it 'sets the value under the key' do
16
+ subject.set(key, 'value')
17
+
18
+ expect(subject.data[key]).to eq('value')
19
+ end
20
+
21
+ end
22
+
23
+ context 'array key' do
24
+
25
+ let(:key) { ['string1', 'string2'] }
26
+
27
+ it 'sets the value under the key' do
28
+ subject.set(key, 'value')
29
+
30
+ expect(subject.data[String(key)]).to eq('value')
31
+ end
32
+
33
+ end
34
+
35
+ end
36
+
37
+ describe '#get' do
38
+
39
+ context 'string key' do
40
+
41
+ let(:key) { 'string' }
42
+
43
+ it 'gets the value for the key' do
44
+ subject.data[key] = 'value'
45
+
46
+ expect(subject.get(key)).to eq('value')
47
+ end
48
+
49
+ end
50
+
51
+ context 'array key' do
52
+
53
+ let(:key) { ['string1', 'string2'] }
54
+
55
+ it 'gets the value for the key' do
56
+ subject.data[String(key)] = 'value'
57
+
58
+ expect(subject.get(key)).to eq('value')
59
+ end
60
+
61
+ end
62
+
63
+ end
64
+
65
+ describe '#get_or_set' do
66
+
67
+ let(:key) { 'string' }
68
+
69
+ context 'value already present' do
70
+
71
+ it 'gets the value for the key from cache' do
72
+ subject.data[key] = 'value'
73
+
74
+ block_called = false
75
+ value = subject.get_or_set(key) do
76
+ block_called = true
77
+ end
78
+
79
+ expect(value).to eq('value')
80
+ expect(block_called).to eq(false)
81
+ end
82
+
83
+ end
84
+
85
+ context 'value not yet present' do
86
+
87
+ it 'evaluates the block and sets the result' do
88
+ block_called = false
89
+ subject.get_or_set(key) do
90
+ block_called = true
91
+ end
92
+
93
+ expect(block_called).to eq(true)
94
+ expect(subject.data[key]).to eq(true)
95
+ end
96
+
97
+ end
98
+
99
+ end
100
+
101
+ describe 'cache purging' do
102
+
103
+ let(:config) { { max_cache_keys: 3 } }
104
+
105
+ it 'purges the cache when key size arrives at max' do
106
+ subject.set('1', 'foo')
107
+ subject.set('2', 'bar')
108
+ subject.set('3', 'baz')
109
+ subject.set('4', 'boz')
110
+
111
+ expect(subject.data.keys.size).to eq(3)
112
+ end
113
+
114
+ end
115
+
116
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: device_detector
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
  - Mati Sójka
@@ -62,6 +62,7 @@ files:
62
62
  - lib/device_detector.rb
63
63
  - lib/device_detector/bot.rb
64
64
  - lib/device_detector/client.rb
65
+ - lib/device_detector/memory_cache.rb
65
66
  - lib/device_detector/os.rb
66
67
  - lib/device_detector/parser.rb
67
68
  - lib/device_detector/version.rb
@@ -75,6 +76,7 @@ files:
75
76
  - regexes/mobile_apps.yml
76
77
  - regexes/oss.yml
77
78
  - regexes/pim.yml
79
+ - spec/device_detector/memory_cache_spec.rb
78
80
  - spec/device_detector/version_extractor_spec.rb
79
81
  - spec/device_detector_spec.rb
80
82
  - spec/spec_helper.rb
@@ -103,6 +105,7 @@ signing_key:
103
105
  specification_version: 4
104
106
  summary: Universal Device Detection
105
107
  test_files:
108
+ - spec/device_detector/memory_cache_spec.rb
106
109
  - spec/device_detector/version_extractor_spec.rb
107
110
  - spec/device_detector_spec.rb
108
111
  - spec/spec_helper.rb