sypex_geo 0.1.0 → 0.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: bb5f6cd269004ce0ac3ef7c190008ca0341cb219
4
- data.tar.gz: b1fbd13bb57fc6fb0fff8c118d77719f5856ee38
3
+ metadata.gz: 270f6a9db891c1f4f6235ca37888a22a1cb6aa18
4
+ data.tar.gz: 2882b224bdbc064f518858245d670df34b08225b
5
5
  SHA512:
6
- metadata.gz: 12c16308ea7729916da8279fa967669a6b9083b9c4d352e7156ca1aa36a13a64d1b23ca4fe80ca166f544e6195510cf31144046038255df078d71ca922a0be08
7
- data.tar.gz: 9fac5ecb9febc9751efb7892a469ba92c0c2b97ab5839c60548cd2cc2fc5b833148408b7999861e88cf7bdd0dd20c865d580ea19cf891d7f5696a3e0d1e205b2
6
+ metadata.gz: 0cbf268f45fcf101a93e837a82567ce4b824be475e20c86c12e5b9d2211619c707b68d13ff1d209ce2d18b7f25966d26b0d3b2fe69f363004bb4a2e70551a67f
7
+ data.tar.gz: 189c75e6d57c803d169c74f36f711b02a38545a132f5e81512775e14545e7af5a4436861aafa5caad480203930619b62a95e022014419be00a1773979ab2dbfc
data/.travis.yml CHANGED
@@ -6,11 +6,13 @@ rvm:
6
6
  - 2.1.0
7
7
 
8
8
  env:
9
- - SYPEXGEO_CITY_MAX_DB=sypexgeo_city_max.dat
9
+ - SXGEO_DB=SxGeo.dat
10
+ - SXGEO_CITY_DB=SxGeoCity.dat
10
11
 
11
12
  before_script:
12
- - '[ -f sypexgeo_city_max.dat ] || (wget http://sypexgeo.net/files/SxGeoCityMax_utf8.zip && unzip SxGeoCityMax_utf8.zip && mv SxGeoCityMax.dat sypexgeo_city_max.dat)'
13
+ - 'wget http://sypexgeo.net/files/SxGeoCountry.zip && unzip SxGeoCountry.zip'
14
+ - 'wget http://sypexgeo.net/files/SxGeoCity_utf8.zip && unzip SxGeoCity_utf8.zip'
13
15
 
14
16
  addons:
15
17
  code_climate:
16
- repo_token: 15e49d47a87a130d4a3cde0bb2ce14fe9053f525e92495a215027aff3e552368
18
+ repo_token: 15e49d47a87a130d4a3cde0bb2ce14fe9053f525e92495a215027aff3e552368
data/README.md CHANGED
@@ -1,4 +1,7 @@
1
1
  [![Gem Version](https://badge.fury.io/rb/sypex_geo.svg)](http://badge.fury.io/rb/sypex_geo)
2
+ [![Build Status](https://travis-ci.org/kolesnikovde/sypex_geo.svg?branch=master)](https://travis-ci.org/kolesnikovde/sypex_geo)
3
+ [![Code Climate](https://codeclimate.com/github/kolesnikovde/sypex_geo/badges/gpa.svg)](https://codeclimate.com/github/kolesnikovde/sypex_geo)
4
+ [![Test Coverage](https://codeclimate.com/github/kolesnikovde/sypex_geo/badges/coverage.svg)](https://codeclimate.com/github/kolesnikovde/sypex_geo)
2
5
 
3
6
  # SypexGeo
4
7
 
@@ -19,65 +22,43 @@ And then execute:
19
22
  ```ruby
20
23
  require 'sypex_geo'
21
24
 
22
- db = SypexGeo::Database.new('./sypex_geo_city_max.dat')
23
- db.lookup(<IPv4 address>)
25
+ db = SypexGeo::Database.new('./SxGeoCity.dat')
26
+ location = db.query('<IPv4 address>')
27
+
28
+ location.city
29
+ # => {
30
+ # id: 524901,
31
+ # lat: 55.75222,
32
+ # lon: 37.61556,
33
+ # name_ru: 'Москва',
34
+ # name_en: 'Moscow'
35
+ # }
36
+
37
+ location.region
24
38
  # => {
25
- # city: {
26
- # id: 524901,
27
- # lat: 55,
28
- # lon: 37,
29
- # name_ru: 'Москва',
30
- # name_en: 'Moscow',
31
- # okato: '45'
32
- # },
33
- # country: {
34
- # id: 185,
35
- # iso: 'RU'
36
- # },
37
- # region: nil
39
+ # id: 524894,
40
+ # iso: 'RU-MOW',
41
+ # name_ru: 'Москва',
42
+ # name_en: 'Moskva'
38
43
  # }
39
44
 
40
- # Query details.
41
- db.lookup(<IPv4 address>, true)
45
+ location.country
42
46
  # => {
43
- # city: {
44
- # id: 524901,
45
- # lat: 55,
46
- # lon: 37,
47
- # name_ru: 'Москва',
48
- # name_en: 'Moscow',
49
- # okato: '45'
50
- # },
51
- # region: {
52
- # id: 524894,
53
- # name_ru: 'Москва',
54
- # name_en: 'Moskva',
55
- # lat: 55,
56
- # lon: 37,
57
- # iso: 'RU-MOW',
58
- # timezone: 'Europe/Moscow',
59
- # okato: '45'
60
- # },
61
- # country: {
62
- # id: 185,
63
- # iso: 'RU',
64
- # continent: 'EU',
65
- # lat: 60,
66
- # lon: 100,
67
- # name_ru: 'Россия',
68
- # name_en: 'Russia',
69
- # timezone: 'Europe/Moscow'
70
- # }
47
+ # id: 185,
48
+ # iso: 'RU',
49
+ # lat: 60.0,
50
+ # lon: 100.0,
51
+ # name_ru: 'Россия',
52
+ # name_en: 'Russia'
71
53
  # }
72
54
 
73
- # "memory_mode"
74
- db = SypexGeo::MemoryDatabase.new('./sypex_geo_city_max.dat')
75
- db.lookup(<IPv4 address>)
55
+ location.country_code
56
+ # => 'RU'
76
57
  ```
77
58
 
78
59
  ## Testing
79
60
 
80
- $ SYPEXGEO_CITY_MAX_DB=./sypexgeo_city_max.dat rspec
61
+ $ SXGEO_DB=./SxGeo.dat SXGEO_CITY_DB=./SxGeoCity.dat rspec
81
62
 
82
63
  ## License
83
64
 
@@ -5,159 +5,126 @@ module SypexGeo
5
5
  end
6
6
 
7
7
  class Database
8
- attr_reader :version
8
+ TYPE_COUNTRY = 1
9
+ TYPE_CITY = 3
10
+
11
+ attr_reader :version, :time
9
12
 
10
13
  def initialize(path)
11
14
  @file = File.open(path, 'rb')
12
15
 
13
- setup!
16
+ parse_header
17
+ setup
14
18
  end
15
19
 
16
- def lookup(ip, full = false)
17
- if seek = search(ip)
18
- read_location(seek, full)
20
+ def query(ip)
21
+ if position = search(ip)
22
+ Result.new(position, self)
19
23
  end
20
24
  end
21
25
 
26
+ def read_country(position)
27
+ @country_parser ||= Pack.new(@country_pack)
28
+ @country_parser.parse(@cities_db[position, @country_size])
29
+ end
30
+
31
+ def read_region(position)
32
+ @region_parser ||= Pack.new(@region_pack)
33
+ @region_parser.parse(@regions_db[position, @region_size])
34
+ end
35
+
36
+ def read_city(position)
37
+ @city_parser ||= Pack.new(@city_pack)
38
+ @city_parser.parse(@cities_db[position, @city_size])
39
+ end
40
+
41
+ def country?
42
+ @type == TYPE_COUNTRY
43
+ end
44
+
45
+ def city?
46
+ @type == TYPE_CITY
47
+ end
48
+
22
49
  def inspect
23
50
  "#<#{self.class}:0x#{object_id} @version=#{@version}>"
24
51
  end
25
52
 
26
53
  protected
27
54
 
28
- def setup!
55
+ def parse_header
29
56
  if header = @file.read(40)
30
57
  id, @version, @time, @type, @charset,
31
- @b_idx_len, @m_idx_len, @range, @db_items, @id_len,
32
- @max_region, @max_city, @region_size, @city_size,
33
- @max_country, @country_size,
58
+ @block_idx_size, @main_idx_size, @range, @db_records_count, @id_size,
59
+ @region_size, @city_size, @regions_db_size, @cities_db_size,
60
+ @country_size, @countries_db_size,
34
61
  @pack_size = header.unpack('a3CNCCCnnNCnnNNnNn')
35
62
  end
36
63
 
37
64
  raise DatabaseError.new, 'Wrong file format' unless id == 'SxG'
65
+ end
38
66
 
67
+ def setup
39
68
  @pack = @file.read(@pack_size).split("\0")
40
- @b_idx_arr = @file.read(@b_idx_len * 4).unpack('N*')
41
- @m_idx_arr = @file.read(@m_idx_len * 4).scan(/.{1,4}/m)
69
+ @country_pack, @region_pack, @city_pack = @pack
70
+
71
+ @block_idx = @file.read(@block_idx_size * 4).unpack('N*')
72
+ @main_idx = @file.read(@main_idx_size * 4).scan(/.{1,4}/m)
42
73
 
43
- @block_len = 3 + @id_len
44
- @db_begin = @file.tell
45
- @regions_begin = @db_begin + @db_items * @block_len
46
- @cities_begin = @regions_begin + @region_size
74
+ @db_record_size = 3 + @id_size
75
+ @db = @file.read(@db_records_count * @db_record_size)
76
+ @regions_db = @file.read(@regions_db_size) if @regions_db_size > 0
77
+ @cities_db = @file.read(@cities_db_size) if @cities_db_size > 0
47
78
  end
48
79
 
49
80
  def search(ip)
50
- ip1n = ip.to_i
51
-
52
- return if ip1n == 0 or ip1n == 127 or ip1n >= 224
81
+ octet = ip.to_i
82
+ return if octet == 0 or octet == 127 or octet >= @block_idx_size
53
83
 
84
+ min, max = @block_idx[octet - 1], @block_idx[octet]
85
+ range = @range
54
86
  ipn = IPAddr.new(ip).hton
55
- blocks_min, blocks_max = @b_idx_arr[ip1n - 1], @b_idx_arr[ip1n]
56
-
57
- if blocks_max - blocks_min > @range
58
- part = search_idx(ipn, blocks_min / @range, (blocks_max / @range) - 1)
59
- min = part > 0 ? part * @range : 0
60
- max = part > @m_idx_len ? @db_items : (part + 1) * @range
61
- min = blocks_min if min < blocks_min
62
- max = blocks_max if max > blocks_max
63
- else
64
- min = blocks_min
65
- max = blocks_max
87
+
88
+ if max - min > range
89
+ min = range * main_idx_search(ipn, min / range, (max / range) - 1)
90
+ max = min + range
66
91
  end
67
92
 
68
- search_db(ipn, min, max)
93
+ db_search(ipn, min, max)
69
94
  end
70
95
 
71
- def search_idx(ipn, min, max)
72
- idx = @m_idx_arr
96
+ def main_idx_search(ipn, min, max)
97
+ idx = @main_idx
73
98
 
74
- while max - min > 8
75
- offset = (min + max) >> 1
99
+ while min < max
100
+ mid = (min + max) / 2
76
101
 
77
- if ipn > idx[offset]
78
- min = offset
102
+ if ipn > idx[mid]
103
+ min = mid + 1
79
104
  else
80
- max = offset
105
+ max = mid
81
106
  end
82
107
  end
83
108
 
84
- while ipn > idx[min]
85
- break if min >= max
86
- min += 1
87
- end
88
-
89
109
  min
90
110
  end
91
111
 
92
- def search_db(ipn, min, max)
93
- len = max - min
94
- @file.pos = @db_begin + min * @block_len
95
- search_db_chunk(@file.read(len * @block_len), ipn, 0, len - 1)
96
- end
97
-
98
- def search_db_chunk(data, ipn, min, max)
99
- block_len = @block_len
112
+ def db_search(ipn, min, max)
113
+ db = @db
114
+ db_record_size = @db_record_size
115
+ octets = ipn[1, 3]
100
116
 
101
- if max - min > 1
102
- ipn = ipn[1, 3]
117
+ while min < max
118
+ mid = (min + max) / 2
103
119
 
104
- while max - min > 8
105
- offset = (min + max) >> 1
106
-
107
- if ipn > data[offset * block_len, 3]
108
- min = offset
109
- else
110
- max = offset
111
- end
112
- end
113
-
114
- while ipn >= data[min * block_len, 3]
115
- min += 1
116
- break if min >= max
120
+ if octets > db[mid * db_record_size, 3]
121
+ min = mid + 1
122
+ else
123
+ max = mid
117
124
  end
118
- else
119
- min += 1
120
- end
121
-
122
- data[min * block_len - @id_len, @id_len].unpack('H*').first.hex
123
- end
124
-
125
- def read_data(seek, limit, type)
126
- @file.pos = (type == TYPE_REGION ? @regions_begin : @cities_begin) + seek
127
- Pack.parse(@pack[type], @file.read(limit))
128
- end
129
-
130
- def read_country(seek)
131
- read_data(seek, @max_country, TYPE_COUNTRY)
132
- end
133
-
134
- def read_region(seek)
135
- read_data(seek, @max_region, TYPE_REGION)
136
- end
137
-
138
- def read_city(seek)
139
- read_data(seek, @max_city, TYPE_CITY)
140
- end
141
-
142
- def read_location(seek, full = false)
143
- region = nil
144
- city = nil
145
- country = nil
146
-
147
- if seek < @country_size
148
- country = read_country(seek)
149
- elsif city = read_city(seek)
150
- region_seek = city.delete(:region_seek)
151
- country_id = city.delete(:country_id)
152
- country = { id: country_id, iso: COUNTRY_CODES[country_id - 1] }
153
- end
154
-
155
- if full and region_seek
156
- region = read_region(region_seek)
157
- country = read_country(region.delete(:country_seek))
158
125
  end
159
126
 
160
- { city: city, region: region, country: country }
127
+ db[min * db_record_size - @id_size, @id_size].unpack('H*')[0].hex
161
128
  end
162
129
  end
163
130
  end
@@ -1,28 +1,21 @@
1
1
  module SypexGeo
2
- module Pack
2
+ class Pack
3
3
  def self.parse(pack, data)
4
+ new(pack).parse(data)
5
+ end
6
+
7
+ def initialize(pack)
8
+ @pack = pack
9
+ end
10
+
11
+ def parse(data)
12
+ @data = data
13
+ @pos = 0
4
14
  result = {}
5
- pos = 0
6
-
7
- pack.split('/').each do |p|
8
- type, name = p.split(':')
9
-
10
- if data.nil? or data.empty?
11
- val = type[0] =~ /b|c/ ? '' : 0
12
- else
13
- if type[0] == 'b'
14
- len = data.index("\0", pos) - pos + 1
15
- val = data[pos, len - 1].force_encoding('UTF-8')
16
- else
17
- len = type_length(type)
18
- val = unpack(type, data[pos, len])
19
- val = val[0] if val.is_a?(Array)
20
- end
21
-
22
- pos += len
23
- end
24
-
25
- result[name.to_sym] = val
15
+
16
+ @pack.split('/').each do |part|
17
+ chunk, name = part.split(':')
18
+ result[name.to_sym] = parse_chunk(chunk)
26
19
  end
27
20
 
28
21
  result
@@ -30,33 +23,90 @@ module SypexGeo
30
23
 
31
24
  protected
32
25
 
33
- def self.type_length(type)
34
- case type[0]
35
- when /t|T/ then 1
36
- when /s|S|n/ then 2
37
- when /m|M/ then 3
38
- when 'd' then 8
39
- when 'c' then type[1..-1].to_i
40
- else 4
26
+ def parse_chunk(chunk)
27
+ case chunk[0]
28
+ when 't' then read_int8(chunk)
29
+ when 'T' then read_uint8(chunk)
30
+ when 's' then read_int16(chunk)
31
+ when 'S' then read_uint16(chunk)
32
+ when 'm' then read_int24(chunk)
33
+ when 'M' then read_uint24(chunk)
34
+ when 'i' then read_int32(chunk)
35
+ when 'I' then read_uint32(chunk)
36
+ when 'f' then read_float(chunk)
37
+ when 'd' then read_double(chunk)
38
+ when 'n' then read_decimal16(chunk)
39
+ when 'N' then read_decimal32(chunk)
40
+ when 'c' then read_chars(chunk)
41
+ when 'b' then read_blob(chunk)
41
42
  end
42
43
  end
43
44
 
44
- def self.unpack(type, val)
45
- case type[0]
46
- when 't' then val.unpack('c')
47
- when 'T' then val.unpack('C')
48
- when 's' then val.unpack('s')
49
- when 'S' then val.unpack('S')
50
- when 'm' then (val + (val[2].ord >> 7) > 0 ? "\xFF" : "\0").unpack('l')
51
- when 'M' then (val + "\0").unpack('L')
52
- when 'i' then val.unpack('l')
53
- when 'I' then val.unpack('L')
54
- when 'f' then val.unpack('f')
55
- when 'd' then val.unpack('d')
56
- when 'n' then val.unpack('s')[0] / (10 ** type[1].to_i)
57
- when 'N' then val.unpack('l')[0] / (10 ** type[1].to_i)
58
- when 'c' then val.rstrip
59
- end
45
+ def read(len)
46
+ @pos += len
47
+ @data[@pos - len, len]
48
+ end
49
+
50
+ def read_string(len)
51
+ read(len).strip.force_encoding('UTF-8')
52
+ end
53
+
54
+ def read_int8(chunk)
55
+ read(1).unpack('c')[0]
56
+ end
57
+
58
+ def read_uint8(chunk)
59
+ read(1).unpack('C')[0]
60
+ end
61
+
62
+ def read_int16(chunk)
63
+ read(2).unpack('s')[0]
64
+ end
65
+
66
+ def read_uint16(chunk)
67
+ read(2).unpack('S')[0]
68
+ end
69
+
70
+ def read_int24(chunk)
71
+ raw = read(3)
72
+ raw = raw + (raw[2].ord >> 7) > 0 ? "\xFF" : "\0"
73
+ raw.unpack('l')[0]
74
+ end
75
+
76
+ def read_uint24(chunk)
77
+ (read(3) + "\0").unpack('L')[0]
78
+ end
79
+
80
+ def read_int32(chunk)
81
+ read(4).unpack('l')[0]
82
+ end
83
+
84
+ def read_uint32(chunk)
85
+ read(4).unpack('L')[0]
86
+ end
87
+
88
+ def read_float(chunk)
89
+ read(4).unpack('f')[0]
90
+ end
91
+
92
+ def read_double(chunk)
93
+ read(8).unpack('d')[0]
94
+ end
95
+
96
+ def read_decimal16(chunk)
97
+ read(2).unpack('s')[0].to_f / (10 ** chunk[1].to_i)
98
+ end
99
+
100
+ def read_decimal32(chunk)
101
+ read(4).unpack('l')[0].to_f / (10 ** chunk[1].to_i)
102
+ end
103
+
104
+ def read_chars(chunk)
105
+ read_string(chunk[1..-1].to_i)
106
+ end
107
+
108
+ def read_blob(chunk)
109
+ read_string(@data.index("\0", @pos) - @pos + 1)
60
110
  end
61
111
  end
62
112
  end
@@ -0,0 +1,40 @@
1
+ module SypexGeo
2
+ COUNTRY_CODES = %w[
3
+ AP EU AD AE AF AG AI AL AM AN AO AQ AR AS AT AU AW AZ BA BB BD BE BF BG
4
+ BH BI BJ BM BN BO BR BS BT BV BW BY BZ CA CC CD CF CG CH CI CK CL CM CN
5
+ CO CR CU CV CX CY CZ DE DJ DK DM DO DZ EC EE EG EH ER ES ET FI FJ FK FM
6
+ FO FR FX GA GB GD GE GF GH GI GL GM GN GP GQ GR GS GT GU GW GY HK HM HN
7
+ HR HT HU ID IE IL IN IO IQ IR IS IT JM JO JP KE KG KH KI KM KN KP KR KW
8
+ KY KZ LA LB LC LI LK LR LS LT LU LV LY MA MC MD MG MH MK ML MM MN MO MP
9
+ MQ MR MS MT MU MV MW MX MY MZ NA NC NE NF NG NI NL NO NP NR NU NZ OM PA
10
+ PE PF PG PH PK PL PM PN PR PS PT PW PY QA RE RO RU RW SA SB SC SD SE SG
11
+ SH SI SJ SK SL SM SN SO SR ST SV SY SZ TC TD TF TG TH TJ TK TM TN TO TL
12
+ TR TT TV TW TZ UA UG UM US UY UZ VA VC VE VG VI VN VU WF WS YE YT RS ZA
13
+ ZM ME ZW A1 A2 O1 AX GG IM JE BL MF
14
+ ]
15
+
16
+ class Result
17
+ def initialize(position, database)
18
+ @position = position
19
+ @database = database
20
+ end
21
+
22
+ def city
23
+ @city ||= @database.read_city(@position)
24
+ end
25
+
26
+ def region
27
+ @region ||= @database.read_region(city[:region_seek])
28
+ end
29
+
30
+ def country
31
+ @country ||= @database.read_country(region[:country_seek])
32
+ end
33
+
34
+ def country_code
35
+ @country_code ||= begin
36
+ COUNTRY_CODES[(@database.country? ? @position : city[:country_id]) - 1]
37
+ end
38
+ end
39
+ end
40
+ end
@@ -1,3 +1,3 @@
1
1
  module SypexGeo
2
- VERSION = '0.1.0'
2
+ VERSION = '0.2.0'
3
3
  end
data/lib/sypex_geo.rb CHANGED
@@ -1,24 +1,4 @@
1
1
  require 'sypex_geo/database'
2
- require 'sypex_geo/memory_database'
2
+ require 'sypex_geo/result'
3
3
  require 'sypex_geo/pack'
4
4
  require 'sypex_geo/version'
5
-
6
- module SypexGeo
7
- TYPE_COUNTRY = 0
8
- TYPE_REGION = 1
9
- TYPE_CITY = 2
10
-
11
- COUNTRY_CODES = %w[
12
- AP EU AD AE AF AG AI AL AM AN AO AQ AR AS AT AU AW AZ BA BB BD BE BF BG
13
- BH BI BJ BM BN BO BR BS BT BV BW BY BZ CA CC CD CF CG CH CI CK CL CM CN
14
- CO CR CU CV CX CY CZ DE DJ DK DM DO DZ EC EE EG EH ER ES ET FI FJ FK FM
15
- FO FR FX GA GB GD GE GF GH GI GL GM GN GP GQ GR GS GT GU GW GY HK HM HN
16
- HR HT HU ID IE IL IN IO IQ IR IS IT JM JO JP KE KG KH KI KM KN KP KR KW
17
- KY KZ LA LB LC LI LK LR LS LT LU LV LY MA MC MD MG MH MK ML MM MN MO MP
18
- MQ MR MS MT MU MV MW MX MY MZ NA NC NE NF NG NI NL NO NP NR NU NZ OM PA
19
- PE PF PG PH PK PL PM PN PR PS PT PW PY QA RE RO RU RW SA SB SC SD SE SG
20
- SH SI SJ SK SL SM SN SO SR ST SV SY SZ TC TD TF TG TH TJ TK TM TN TO TL
21
- TR TT TV TW TZ UA UG UM US UY UZ VA VC VE VG VI VN VU WF WS YE YT RS ZA
22
- ZM ME ZW A1 A2 O1 AX GG IM JE BL MF
23
- ]
24
- end
@@ -1,5 +1,8 @@
1
+ # coding: utf-8
2
+
1
3
  require './spec/spec_helper'
2
4
  require 'sypex_geo'
5
+ require 'ipaddr'
3
6
 
4
7
  describe SypexGeo do
5
8
  let(:demo_ip) do
@@ -7,111 +10,128 @@ describe SypexGeo do
7
10
  '80.90.64.1'
8
11
  end
9
12
 
10
- let(:default_db_file) do
11
- File.expand_path(__FILE__ + '/../support/sypexgeo_city_max.dat')
12
- end
13
-
14
13
  let(:invalid_db_file) do
15
14
  File.expand_path(__FILE__ + '/../support/invalid.dat')
16
15
  end
17
16
 
18
- let(:db_file) do
19
- ENV['SYPEXGEO_CITY_MAX_DB'] || default_db_file
20
- end
17
+ shared_examples 'geo db' do
18
+ describe '#initialize' do
19
+ it 'raises error if database is invalid' do
20
+ expect do
21
+ described_class.new(invalid_db_file)
22
+ end.to raise_error(SypexGeo::DatabaseError)
23
+ end
24
+ end
21
25
 
22
- let(:city_info) do
23
- {
24
- city: {
25
- id: 524901,
26
- lat: 55,
27
- lon: 37,
28
- name_ru: 'Москва',
29
- name_en: 'Moscow',
30
- okato: '45'
31
- },
32
- country: {
33
- id: 185,
34
- iso: 'RU'
35
- },
36
- region: nil
37
- }
26
+ describe '#query' do
27
+ it 'returns nil if IP address is reserved' do
28
+ expect(subject.query('0.0.0.0')).to be_nil
29
+ expect(subject.query('127.0.0.0')).to be_nil
30
+ expect(subject.query('224.0.0.0')).to be_nil
31
+ expect(subject.query('255.0.0.0')).to be_nil
32
+ end
33
+
34
+ it 'raises error if IP address is invalid' do
35
+ error = if IPAddr.const_defined?('InvalidAddressError')
36
+ IPAddr::InvalidAddressError
37
+ else
38
+ ArgumentError
39
+ end
40
+
41
+ expect do
42
+ subject.query('1.invalid')
43
+ end.to raise_error(error)
44
+ end
45
+ end
38
46
  end
39
47
 
40
- let(:location_info) do
41
- {
42
- city: {
48
+ shared_examples 'city db' do
49
+ it_behaves_like 'geo db'
50
+
51
+ let(:city_info) do
52
+ {
43
53
  id: 524901,
44
- lat: 55,
45
- lon: 37,
54
+ country_id: 185,
55
+ lat: 55.75222,
56
+ lon: 37.61556,
46
57
  name_ru: 'Москва',
47
58
  name_en: 'Moscow',
48
- okato: '45'
49
- },
50
- region: {
59
+ region_seek: 11795
60
+ }
61
+ end
62
+
63
+ let(:region_info) do
64
+ {
51
65
  id: 524894,
66
+ iso: 'RU-MOW',
52
67
  name_ru: 'Москва',
53
68
  name_en: 'Moskva',
54
- lat: 55,
55
- lon: 37,
56
- iso: 'RU-MOW',
57
- timezone: 'Europe/Moscow',
58
- okato: '45'
59
- },
60
- country: {
69
+ country_seek: 9128
70
+ }
71
+ end
72
+
73
+ let(:country_info) do
74
+ {
61
75
  id: 185,
62
76
  iso: 'RU',
63
- continent: 'EU',
64
- lat: 60,
65
- lon: 100,
77
+ lat: 60.0,
78
+ lon: 100.0,
66
79
  name_ru: 'Россия',
67
- name_en: 'Russia',
68
- timezone: 'Europe/Moscow'
80
+ name_en: 'Russia'
69
81
  }
70
- }
71
- end
82
+ end
72
83
 
73
- shared_examples 'geoip_database' do
74
- describe '#initialize' do
75
- it 'raises error if database is invalid' do
76
- expect do
77
- subject.class.new(invalid_db_file)
78
- end.to raise_error(SypexGeo::DatabaseError)
79
- end
84
+ let(:country_code) do
85
+ 'RU'
80
86
  end
81
87
 
82
- describe '#lookup' do
83
- it 'returns nil if IP address is reserved' do
84
- expect(subject.lookup('0.0.0.0')).to be_nil
85
- expect(subject.lookup('127.0.0.0')).to be_nil
86
- expect(subject.lookup('224.0.0.0')).to be_nil
87
- expect(subject.lookup('255.0.0.0')).to be_nil
88
- end
88
+ it { should be_city }
89
+ it { should_not be_country }
89
90
 
90
- it 'raises IPAddr::InvalidAddressError if IP address is invalid' do
91
- expect do
92
- subject.lookup('1.invalid')
93
- end.to raise_error(IPAddr::InvalidAddressError)
94
- end
91
+ describe '#query' do
92
+ it 'returns location info' do
93
+ location = subject.query(demo_ip)
95
94
 
96
- it 'returns city info' do
97
- expect(subject.lookup(demo_ip)).to eq(city_info)
95
+ expect(location.city).to eq(city_info)
96
+ expect(location.region).to eq(region_info)
97
+ expect(location.country).to eq(country_info)
98
+ expect(location.country_code).to eq(country_code)
98
99
  end
100
+ end
101
+ end
102
+
103
+ shared_examples 'country db' do
104
+ it_behaves_like 'geo db'
105
+
106
+ let(:country_code) do
107
+ 'RU'
108
+ end
99
109
 
100
- it 'returns detailed location info if specified' do
101
- expect(subject.lookup(demo_ip, true)).to eq(location_info)
110
+ it { should be_country }
111
+ it { should_not be_city }
112
+
113
+ describe '#query' do
114
+ it 'returns country code' do
115
+ expect(subject.query(demo_ip).country_code).to eq(country_code)
102
116
  end
103
117
  end
104
118
  end
105
119
 
106
120
  describe SypexGeo::Database do
107
- subject(:db) { SypexGeo::Database.new(db_file) }
121
+ if ENV['SXGEO_CITY_DB']
122
+ context 'city db' do
123
+ subject { SypexGeo::Database.new(ENV['SXGEO_CITY_DB']) }
108
124
 
109
- it_behaves_like 'geoip_database'
110
- end
125
+ it_behaves_like 'city db'
126
+ end
127
+ end
111
128
 
112
- describe SypexGeo::MemoryDatabase do
113
- subject(:db) { SypexGeo::MemoryDatabase.new(db_file) }
129
+ if ENV['SXGEO_DB']
130
+ context 'country db' do
131
+ subject { SypexGeo::Database.new(ENV['SXGEO_DB']) }
114
132
 
115
- it_behaves_like 'geoip_database'
133
+ it_behaves_like 'country db'
134
+ end
135
+ end
116
136
  end
117
137
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sypex_geo
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kolesnikov Danil
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-08-22 00:00:00.000000000 Z
11
+ date: 2014-09-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -82,8 +82,8 @@ files:
82
82
  - Rakefile
83
83
  - lib/sypex_geo.rb
84
84
  - lib/sypex_geo/database.rb
85
- - lib/sypex_geo/memory_database.rb
86
85
  - lib/sypex_geo/pack.rb
86
+ - lib/sypex_geo/result.rb
87
87
  - lib/sypex_geo/version.rb
88
88
  - spec/spec_helper.rb
89
89
  - spec/support/invalid.dat
@@ -109,7 +109,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
109
109
  version: '0'
110
110
  requirements: []
111
111
  rubyforge_project:
112
- rubygems_version: 2.2.2
112
+ rubygems_version: 2.2.1
113
113
  signing_key:
114
114
  specification_version: 4
115
115
  summary: Sypex Geo IP database adapter for Ruby.
@@ -1,20 +0,0 @@
1
- module SypexGeo
2
- class MemoryDatabase < Database
3
- def setup!
4
- super
5
-
6
- @db = @file.read(@db_items * @block_len)
7
- @regions_db = @file.read(@region_size) if @region_size > 0
8
- @cities_db = @file.read(@city_size) if @city_size > 0
9
- end
10
-
11
- def search_db(ipn, min, max)
12
- search_db_chunk(@db, ipn, min, max)
13
- end
14
-
15
- def read_data(seek, limit, type)
16
- raw = (type == TYPE_REGION ? @regions_db : @cities_db)[seek, limit]
17
- Pack.parse(@pack[type], raw)
18
- end
19
- end
20
- end