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 +4 -4
- data/README.md +13 -0
- data/lib/device_detector.rb +28 -0
- data/lib/device_detector/memory_cache.rb +56 -0
- data/lib/device_detector/parser.rb +12 -5
- data/lib/device_detector/version.rb +1 -1
- data/spec/device_detector/memory_cache_spec.rb +116 -0
- metadata +4 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3831c0674436e07a0133b2396d2662d9e7c557e7
|
4
|
+
data.tar.gz: 913f71fc60c565076298fd8eb9a6c93184e048b0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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 )
|
data/lib/device_detector.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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(
|
36
|
+
File.join(ROOT, 'regexes', filename)
|
33
37
|
end
|
34
38
|
end
|
35
39
|
|
36
|
-
|
37
|
-
|
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
|
@@ -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.
|
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
|