geoipdb 0.1.4

Sign up to get free protection for your applications and to get access to all the features.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/Gemfile ADDED
@@ -0,0 +1,13 @@
1
+ source "http://rubygems.org"
2
+ # Add dependencies required to use your gem here.
3
+ # Example:
4
+ # gem "activesupport", ">= 2.3.5"
5
+
6
+ # Add dependencies to develop your gem here.
7
+ # Include everything needed to run rake, tests, features, etc.
8
+ group :development do
9
+ gem "rspec", "~> 2.1.0"
10
+ gem "bundler", "~> 1.0.0"
11
+ gem "jeweler", "~> 1.5.1"
12
+ gem "rcov", ">= 0"
13
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010 Eugen Martin
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.markdown ADDED
@@ -0,0 +1,18 @@
1
+ # geoipdb: fast (in memory!) geo location db.
2
+
3
+ Fast (>3 Mio queries/sec!!!) GeoIpDb implementation for Ruby using C-Extensions.
4
+
5
+ * Returns a GeoLocation to a given IP.
6
+ * Reads Data from CSV-Files and uses internal binary caching.
7
+
8
+ ## Usage
9
+
10
+ db = GeoIpDb.init "city_codes.csv", "ip_city.txt", "ip_city.cache"
11
+ location_hash = db.city_by_ip("178.0.0.1")
12
+ # => {"name"=>"eschborn", "country"=>"deu", "lng"=>8.55, "lat"=>50.133333}
13
+
14
+ == Copyright
15
+
16
+ Copyright (c) 2010 Eugen Martin. See LICENSE.txt for
17
+ further details.
18
+
data/Rakefile ADDED
@@ -0,0 +1,48 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ begin
4
+ Bundler.setup(:default, :development)
5
+ rescue Bundler::BundlerError => e
6
+ $stderr.puts e.message
7
+ $stderr.puts "Run `bundle install` to install missing gems"
8
+ exit e.status_code
9
+ end
10
+ require 'rake'
11
+
12
+ require 'jeweler'
13
+ Jeweler::Tasks.new do |gem|
14
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
15
+ gem.name = "geoipdb"
16
+ gem.homepage = "http://github.com/olgen/geoipdb"
17
+ gem.license = "MIT"
18
+ gem.summary = %Q{Fast (>3 Mio queries/sec!!!) GeoIpDb implementation for Ruby using C-Extensions.}
19
+ gem.description = %Q{Returns a GeoLocation to a given IP. Reads Data from CSV-Files and uses internal binary caching.}
20
+ gem.email = "eugeniusmartinus@googlemail.com"
21
+ gem.authors = ["Eugen Martin"]
22
+ gem.extensions = ['ext/extconf.rb']
23
+ gem.require_path = "ext"
24
+ end
25
+ Jeweler::RubygemsDotOrgTasks.new
26
+
27
+ require 'rspec/core'
28
+ require 'rspec/core/rake_task'
29
+ RSpec::Core::RakeTask.new(:spec) do |spec|
30
+ spec.pattern = FileList['spec/**/*_spec.rb']
31
+ end
32
+
33
+ RSpec::Core::RakeTask.new(:rcov) do |spec|
34
+ spec.pattern = 'spec/**/*_spec.rb'
35
+ spec.rcov = true
36
+ end
37
+
38
+ task :default => :spec
39
+
40
+ require 'rake/rdoctask'
41
+ Rake::RDocTask.new do |rdoc|
42
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
43
+
44
+ rdoc.rdoc_dir = 'rdoc'
45
+ rdoc.title = "geoipdb #{version}"
46
+ rdoc.rdoc_files.include('README*')
47
+ rdoc.rdoc_files.include('lib/**/*.rb')
48
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.4
data/ext/build.sh ADDED
@@ -0,0 +1 @@
1
+ gcc -Wall -o test test.c ipdb.c ipdb.h
data/ext/extconf.rb ADDED
@@ -0,0 +1,5 @@
1
+ require "mkmf"
2
+
3
+ extension_name = "geoipdb"
4
+ dir_config(extension_name)
5
+ create_makefile(extension_name)
data/ext/geoipdb.c ADDED
@@ -0,0 +1,109 @@
1
+ #include "ipdb.h"
2
+
3
+ #include "ruby.h"
4
+
5
+ /**
6
+ Ruby Wrapper
7
+ */
8
+
9
+ typedef struct geipdb {
10
+ IPDB *db;
11
+ } geoipdb;
12
+
13
+ static VALUE cGeoIpDb;
14
+
15
+ VALUE method_hello_world(VALUE self);
16
+
17
+ // free the memory used by the db, called by the Ruby-GC
18
+ void geoipdb_free(geoipdb *gi) {
19
+ printf("Freeing memory for the GeoIpDb.. \n");
20
+ if(gi == NULL)
21
+ return;
22
+ if (gi->db != NULL){
23
+ if(gi->db->cities != NULL ){
24
+ printf("..freeing cities \n");
25
+ free(gi->db->cities);
26
+ gi->db->cities = NULL;
27
+ }
28
+ if(gi->db->ranges != NULL ){
29
+ printf("..freeing ranges \n");
30
+ free(gi->db->ranges);
31
+ gi->db->ranges = NULL;
32
+ }
33
+ if(gi->db != NULL){
34
+ printf("..freeing ipdb \n");
35
+ free(gi->db);
36
+ }
37
+ }
38
+ }
39
+
40
+
41
+ VALUE ipdb_init(VALUE self, VALUE cities_file_name, VALUE ranges_file_name, VALUE cache_file_name) {
42
+ geoipdb *gi;
43
+
44
+ Check_Type(cities_file_name, T_STRING);
45
+ Check_Type(ranges_file_name, T_STRING);
46
+ Check_Type(cache_file_name, T_STRING);
47
+
48
+ char *cities_csv_file = RSTRING(cities_file_name)->ptr;
49
+ char *ranges_csv_file = RSTRING(ranges_file_name)->ptr;
50
+ char *cache_file = RSTRING(cache_file_name)->ptr;
51
+
52
+ gi = malloc(sizeof(geoipdb));
53
+
54
+ gi->db= init_db(cities_csv_file, ranges_csv_file, cache_file);
55
+
56
+ if(gi->db == NULL)
57
+ {
58
+ if(DEBUG)
59
+ printf("Could not init DB!\n");
60
+ /*
61
+ TODO: Add geoipdb_free in this case.. though not important for production...
62
+ */
63
+ return Qnil;
64
+ }else{
65
+ if(DEBUG)
66
+ printf("\nDB Init completed!\n");
67
+ return(Data_Wrap_Struct(cGeoIpDb, 0, geoipdb_free, gi));
68
+ }
69
+ }
70
+
71
+
72
+ VALUE city_to_hash(City * city){
73
+ VALUE hash = rb_hash_new();
74
+ // return a ruby-hash with the needed data
75
+ rb_hash_aset(hash, rb_str_new2("name"), rb_str_new2(city->name));
76
+ rb_hash_aset(hash, rb_str_new2("country"), rb_str_new2(city->country_iso2));
77
+ rb_hash_aset(hash, rb_str_new2("lat"), rb_float_new(city->lat));
78
+ rb_hash_aset(hash, rb_str_new2("lng"), rb_float_new(city->lng));
79
+ rb_hash_aset(hash, rb_str_new2("city_code"), INT2FIX(city->city_code));
80
+ return hash;
81
+ }
82
+
83
+ VALUE ipdb_city_by_ip(VALUE self, VALUE ip_string){
84
+ char *ip = RSTRING(ip_string)->ptr;
85
+ geoipdb *gi;
86
+
87
+ Data_Get_Struct(self, geoipdb, gi);
88
+ City * city = city_by_ip(gi->db, ip);
89
+ if(city == NULL)
90
+ {
91
+ // printf("Could not find city for ip: %s\n", ip);
92
+ return Qnil;
93
+ }else{
94
+ // printf("Found city for ip: %s\n", ip);
95
+ // print_city(city);
96
+ return city_to_hash(city);
97
+ // create a hash to return to ruby:
98
+ }
99
+ }
100
+
101
+
102
+
103
+ void Init_geoipdb(void)
104
+ {
105
+ //Erstelle die Klasse cGeoIpDb als Subklasse von Object
106
+ cGeoIpDb = rb_define_class( "GeoIpDb", rb_cObject);
107
+ rb_define_singleton_method( cGeoIpDb, "init", ipdb_init, 3);
108
+ rb_define_method( cGeoIpDb, "city_by_ip", ipdb_city_by_ip, 1);
109
+ }
data/ext/ipdb.c ADDED
@@ -0,0 +1,604 @@
1
+ #include "ipdb.h"
2
+
3
+ #include <search.h>
4
+ #include <stdio.h>
5
+ #include <string.h>
6
+ #include <stdlib.h>
7
+ #include <sys/time.h>
8
+
9
+
10
+
11
+
12
+ const char country_iso2_codes[253][3] = { "--","ap","eu","ad","ae","af","ag","ai","al","am","an",
13
+ "ao","aq","ar","as","at","au","aw","az","ba","bb",
14
+ "bd","be","bf","bg","bh","bi","bj","bm","bn","bo",
15
+ "br","bs","bt","bv","bw","by","bz","ca","cc","cd",
16
+ "cf","cg","ch","ci","ck","cl","cm","cn","co","cr",
17
+ "cu","cv","cx","cy","cz","de","dj","dk","dm","do",
18
+ "dz","ec","ee","eg","eh","er","es","et","fi","fj",
19
+ "fk","fm","fo","fr","fx","ga","gb","gd","ge","gf",
20
+ "gh","gi","gl","gm","gn","gp","gq","gr","gs","gt",
21
+ "gu","gw","gy","hk","hm","hn","hr","ht","hu","id",
22
+ "ie","il","in","io","iq","ir","is","it","jm","jo",
23
+ "jp","ke","kg","kh","ki","km","kn","kp","kr","kw",
24
+ "ky","kz","la","lb","lc","li","lk","lr","ls","lt",
25
+ "lu","lv","ly","ma","mc","md","mg","mh","mk","ml",
26
+ "mm","mn","mo","mp","mq","mr","ms","mt","mu","mv",
27
+ "mw","mx","my","mz","na","nc","ne","nf","ng","ni",
28
+ "nl","no","np","nr","nu","nz","om","pa","pe","pf",
29
+ "pg","ph","pk","pl","pm","pn","pr","ps","pt","pw",
30
+ "py","qa","re","ro","ru","rw","sa","sb","sc","sd",
31
+ "se","sg","sh","si","sj","sk","sl","sm","sn","so",
32
+ "sr","st","sv","sy","sz","tc","td","tf","tg","th",
33
+ "tj","tk","tm","tn","to","tl","tr","tt","tv","tw",
34
+ "tz","ua","ug","um","us","uy","uz","va","vc","ve",
35
+ "vg","vi","vn","vu","wf","ws","ye","yt","rs","za",
36
+ "zm","me","zw","a1","a2","o1","ax","gg","im","je",
37
+ "bl","mf"};
38
+
39
+ static const unsigned num_countries = (unsigned)(sizeof(country_iso2_codes)/sizeof(country_iso2_codes[0]));
40
+
41
+ const char country_iso3_codes[253][4] = { "--","ap","eu","and","are","afg","atg","aia","alb","arm","ant",
42
+ "ago","aq","arg","asm","aut","aus","abw","aze","bih","brb",
43
+ "bgd","bel","bfa","bgr","bhr","bdi","ben","bmu","brn","bol",
44
+ "bra","bhs","btn","bv","bwa","blr","blz","can","cc","cod",
45
+ "caf","cog","che","civ","cok","chl","cmr","chn","col","cri",
46
+ "cub","cpv","cx","cyp","cze","deu","dji","dnk","dma","dom",
47
+ "dza","ecu","est","egy","esh","eri","esp","eth","fin","fji",
48
+ "flk","fsm","fro","fra","fx","gab","gbr","grd","geo","guf",
49
+ "gha","gib","grl","gmb","gin","glp","gnq","grc","gs","gtm",
50
+ "gum","gnb","guy","hkg","hm","hnd","hrv","hti","hun","idn",
51
+ "irl","isr","ind","io","irq","irn","isl","ita","jam","jor",
52
+ "jpn","ken","kgz","khm","kir","com","kna","prk","kor","kwt",
53
+ "cym","kaz","lao","lbn","lca","lie","lka","lbr","lso","ltu",
54
+ "lux","lva","lby","mar","mco","mda","mdg","mhl","mkd","mli",
55
+ "mmr","mng","mac","mnp","mtq","mrt","msr","mlt","mus","mdv",
56
+ "mwi","mex","mys","moz","nam","ncl","ner","nfk","nga","nic",
57
+ "nld","nor","npl","nru","niu","nzl","omn","pan","per","pyf",
58
+ "png","phl","pak","pol","spm","pcn","pri","pse","prt","plw",
59
+ "pry","qat","reu","rou","rus","rwa","sau","slb","syc","sdn",
60
+ "swe","sgp","shn","svn","sjm","svk","sle","smr","sen","som",
61
+ "sur","stp","slv","syr","swz","tca","tcd","tf","tgo","tha",
62
+ "tjk","tkl","tkm","tun","ton","tls","tur","tto","tuv","twn",
63
+ "tza","ukr","uga","um","usa","ury","uzb","vat","vct","ven",
64
+ "vgb","vir","vnm","vut","wlf","wsm","yem","yt","srb","zaf",
65
+ "zmb","mne","zwe","a1","a2","o1","ala","ggy","imn","jey",
66
+ "blm","maf"};
67
+
68
+
69
+ void
70
+ print_range(const IpRange* e){
71
+ printf( "from: %lu, to:%lu ->City-idx: %i \n",e->from, e->to,e->city_index );
72
+ }
73
+
74
+ void
75
+ print_ranges(IPDB * db){
76
+ int i;
77
+ for(i = 0; i < db->ranges_count; ++i)
78
+ {
79
+ print_range(&(db->ranges[i]));
80
+ }
81
+ }
82
+
83
+ void
84
+ print_city(const City * e){
85
+ if(e == NULL)
86
+ {
87
+ return;
88
+ }
89
+ printf( "City: code:%i, name:%s, country: %s, lat: %10.7f, lng: %10.7f \n",e->city_code, e->name, e->country_iso3, e->lat, e->lng );
90
+ }
91
+
92
+ void
93
+ print_cities(IPDB * db){
94
+ int i;
95
+ for(i = 0; i < db->cities_count; ++i)
96
+ {
97
+ print_city(&(db->cities[i]));
98
+ }
99
+ }
100
+
101
+
102
+
103
+ void print_stats(IPDB * db){
104
+ printf("DB STATS: \n");
105
+ printf("\tCities: %i\n", db->cities_count);
106
+ printf("\tRanges: %i\n", db->ranges_count);
107
+ }
108
+
109
+ double
110
+ get_time(struct timeval *tim){
111
+ gettimeofday(tim, NULL);
112
+ return tim->tv_sec+(tim->tv_usec/1000000.0);
113
+ }
114
+
115
+
116
+ unsigned long
117
+ ip_to_int(const char *addr){
118
+ unsigned int c, octet, t;
119
+ unsigned long ipnum;
120
+ int i = 3;
121
+
122
+ octet = ipnum = 0;
123
+ while ((c = *addr++)) {
124
+ if (c == '.') {
125
+ if (octet > 255)
126
+ return 0;
127
+ ipnum <<= 8;
128
+ ipnum += octet;
129
+ i--;
130
+ octet = 0;
131
+ } else {
132
+ t = octet;
133
+ octet <<= 3;
134
+ octet += t;
135
+ octet += t;
136
+ c -= '0';
137
+ if (c > 9)
138
+ return 0;
139
+ octet += c;
140
+ }
141
+ }
142
+ if ((octet > 255) || (i != 0))
143
+ return 0;
144
+ ipnum <<= 8;
145
+ return ipnum + octet;
146
+ }
147
+
148
+
149
+ // Function to compare
150
+ // - either two ip-ranges: i.e.: a(from...to) <=> b(from...to)
151
+ // - or a ip(i.e. range without to) and an ip-range: i.e. a(from...NULL) <=> b(from...to); a(from...to) <=> b(from ... NULL)
152
+ int compare_ranges(const void *fa, const void *fb) {
153
+ if(fa == NULL)
154
+ {
155
+ printf("FA IS NULL!!!\n");
156
+ return 0;
157
+ }
158
+ if(fb == NULL)
159
+ {
160
+ printf("FB IS NULL!!!\n");
161
+ return 0;
162
+ }
163
+
164
+
165
+ const IpRange *a = (IpRange *) fa;
166
+ const IpRange *b = (IpRange *) fb;
167
+
168
+ // printf("\tComparing: a:");
169
+ // print_range(a);
170
+ // printf(" with b:");
171
+ // print_range(b);
172
+ // printf("\n");
173
+
174
+ if(a->from>0 && a->to>0 && b->from>0 && b->to>0){ //regular case: both entries are ranges
175
+ if(a->to < b->from) {
176
+ return -1;
177
+ }else if(a->from > b->to){
178
+ return +1;
179
+ }else{
180
+ return 0;
181
+ }
182
+ }else if(a->to == 0 && b->to>0){//a is a search_object
183
+ if(a->from < b->from) {
184
+ return -1;
185
+ }else if(a->from > b->to){
186
+ return +1;
187
+ }else{
188
+ return 0;
189
+ }
190
+ }else if(b->to == 0 && a->to>0){//b is a search_object
191
+ if(b->from < a->from){
192
+ return -1;
193
+ }else if(b->from > a->to){
194
+ return +1;
195
+ }else{
196
+ return 0;
197
+ }
198
+ }else if(a->to == 0 && b->to == 0){ //both are search objects - this should not happen!
199
+ return a->from - b->from;
200
+ }
201
+ return 0;
202
+ }
203
+
204
+
205
+ int
206
+ compare_cities(const void *a, const void *b){
207
+ const City city_a = *(City*)a;
208
+ const City city_b = * (City*) b;
209
+ // sort cities by city_code
210
+ return city_a.city_code - city_b.city_code;
211
+ }
212
+
213
+ void
214
+ sort_cities(IPDB * db){
215
+ if(DEBUG)
216
+ printf("Sorting %i Cities in db...\n", db->cities_count);
217
+
218
+ struct timeval tim;
219
+ double t1 = get_time(&tim);
220
+
221
+ qsort(db->cities,db->cities_count,sizeof(City), compare_cities);
222
+ if(DEBUG)
223
+ printf("\n Sorting cities needed %.6lf seconds\n", get_time(&tim)-t1);
224
+ }
225
+
226
+
227
+ int // returns a city-inde
228
+ city_index_by_code(IPDB * db, uint16 city_code){
229
+ City *search, *result;
230
+ search = malloc(sizeof(City));
231
+ search->city_code = city_code;
232
+ result = (City*) bsearch(search, db->cities, db->cities_count, sizeof(City), compare_cities);
233
+
234
+ if(search != NULL)
235
+ free(search);
236
+
237
+ if(result == NULL)
238
+ {
239
+ if(DEBUG)
240
+ printf("Could not find searched city with code: %i \n", city_code);
241
+ return -1;
242
+ }else{
243
+ // printf("Found result: \t");
244
+ // print_city(result);
245
+ int index;
246
+ index = (result - db->cities);
247
+ // printf("Found index: %i\n", index);
248
+ return index;
249
+ }
250
+ }
251
+
252
+
253
+ City *
254
+ city_by_ip(IPDB *db, char *ip){
255
+ IpRange * search, *result;
256
+ search = (IpRange *)malloc(sizeof(IpRange));
257
+ void * res;
258
+
259
+ // print_stats(db);
260
+
261
+ if(db == NULL)
262
+ {
263
+ printf("ERROR: DB ist NULL! ");
264
+ return NULL;
265
+ }
266
+
267
+ if(db->ranges_count == 0)
268
+ {
269
+ printf("ERROR: DB has no Ranges Data. Can not search!\n");
270
+ return NULL;
271
+ }
272
+
273
+ search->from = ip_to_int(ip);
274
+ search->to=0;
275
+ search->city_index = 0;
276
+ if(DEBUG)
277
+ printf("Searching for: ip=%s, ipnum=%lu \n", ip, search->from);
278
+ // result = (IpRange*) bsearch(search, db->ranges, db->ranges_count-2, sizeof(IpRange), compare_ranges);
279
+ res = bsearch(search, db->ranges, db->ranges_count, sizeof(IpRange), compare_ranges);
280
+ if(search != NULL)
281
+ free(search);
282
+
283
+ if(res == NULL)
284
+ {
285
+ if(DEBUG)
286
+ printf("ERROR: Could not find the IP: %s! THIS SHOULD NOT HAPPEN!\n", ip);
287
+ return NULL;
288
+ }else{
289
+ result = (IpRange*) res;
290
+ if(DEBUG){
291
+ printf("Found Range: \t");
292
+ print_range(result);
293
+ }
294
+ }
295
+
296
+ if(db->cities_count == 0)
297
+ {
298
+ if(DEBUG)
299
+ printf("ERROR: DB has no City Data. Can not search!\n");
300
+ return NULL;
301
+ }
302
+
303
+
304
+ if(result->city_index >0 && result->city_index < db->cities_count)
305
+ {
306
+ // address the city directly via the array-idx
307
+ return &(db->cities[result->city_index]);
308
+ } else {
309
+ if(DEBUG)
310
+ printf("ERROR: Could not find city with index: %i - THIS SHOULD NOT HAPPEN!\n", result->city_index);
311
+ return NULL;
312
+ }
313
+ }
314
+
315
+
316
+ // read ip-ranges from csv file, of format:
317
+ // from_ip|to_ip|city_code
318
+ void
319
+ read_ranges_csv(IPDB * db){
320
+ struct timeval tim;
321
+ double t1 = get_time(&tim);
322
+
323
+ db->ranges = malloc(sizeof(IpRange) * db->max_ranges_count);
324
+
325
+ if(DEBUG)
326
+ printf("Parsing RANGES-CSV-file: %s\n", db->ranges_csv_file);
327
+ FILE * f = fopen(db->ranges_csv_file, "rt");
328
+ if(f == NULL)
329
+ {
330
+ if(DEBUG)
331
+ printf("Could not open the CSV-file: %s", db->ranges_csv_file);
332
+ return;
333
+ }
334
+ char line[256];
335
+ char* from = NULL;
336
+ char* to = NULL;
337
+ char* city_code = NULL;
338
+ int invalid_cities_count = 0;
339
+ int city_index;
340
+
341
+ IpRange* entry;
342
+ db->ranges_count = 0;
343
+ while (fgets(line,sizeof(line),f) && db->ranges_count < db->max_ranges_count){
344
+ if(DEBUG && db->ranges_count % 1000000 == 0)
345
+ printf("Worked lines: %i\n", db->ranges_count);
346
+
347
+ from = strtok(line, RANGES_DELIM);
348
+ to = strtok(NULL, RANGES_DELIM);
349
+ city_code = strtok(NULL, RANGES_DELIM);
350
+ city_index = city_index_by_code(db, atoi(city_code));
351
+ if(city_index < 0)
352
+ {
353
+ if(DEBUG)
354
+ printf("Could not find city for code: %i", atoi(city_code));
355
+ invalid_cities_count ++;
356
+ continue;
357
+ }else{
358
+ entry = &(db->ranges[db->ranges_count]);
359
+
360
+ entry->from = ip_to_int(from);
361
+ entry->to = ip_to_int(to);
362
+ // entry->city_code = atoi(city_code);
363
+
364
+ entry->city_index = city_index;
365
+ // printf("Line: %s", line);
366
+ // printf("from: %u,to: %u, city_code:%i \n",entry->from,entry->to,entry->city_code);
367
+ // db->ranges[i] = *entry;
368
+ // printf("working record nr: %li\n", db->ranges_count);
369
+ db->ranges_count++;
370
+ }
371
+ }
372
+ if(invalid_cities_count)
373
+ {
374
+ printf("Found invalid cities: %i", invalid_cities_count);
375
+ }
376
+ printf("\n Parsing of %i records needed %.6lf seconds\n", db->ranges_count, get_time(&tim)-t1);
377
+ }
378
+
379
+
380
+
381
+ //translate country iso3 to iso2
382
+ char *
383
+ iso2_code(char* iso3){
384
+ int i = 0;
385
+ for( i = 0; i < num_countries; i++)
386
+ {
387
+ // printf("Trying cmp of %s and %s...", iso3, country_iso3_codes[i]);
388
+ if( strcmp(country_iso3_codes[i],iso3)==0)
389
+ {
390
+ return (char*) country_iso2_codes[i];
391
+ }
392
+ }
393
+ // printf("Could not find iso2 code for iso3: %s, using: '%s' \n", iso3, country_iso2_codes[0]);
394
+ return (char*) country_iso2_codes[0];
395
+ }
396
+
397
+ //read city-data from csv-file of format:
398
+ // COUNTRY,REGION,CITY-NAME,METRO-CODE,CITY-CODE,LATITUDE,LONGITUDE
399
+ void
400
+ read_cities_csv(IPDB * db){
401
+ struct timeval tim;
402
+ double t1 = get_time(&tim);
403
+
404
+ db->cities_count = 0;
405
+ db->cities = malloc(sizeof(City) * db->max_cities_count);
406
+
407
+ if(DEBUG)
408
+ printf("Parsing Cities-CSV-file: %s\n", db->cities_csv_file);
409
+ FILE * f = fopen(db->cities_csv_file, "rt");
410
+ if(f == NULL)
411
+ {
412
+ if(DEBUG)
413
+ printf("Could not open the Cities-CSV-file: %s", db->cities_csv_file);
414
+ return;
415
+ }
416
+ char line[256];
417
+ char *country, *region, *name,*metro_code,*city_code,*lat,*lng ;
418
+ int i = 0;
419
+ City* entry;
420
+
421
+ while (fgets(line,sizeof(line),f) && db->cities_count < db->max_cities_count){
422
+ i++;
423
+ if(i == 1)
424
+ continue;//skip the header
425
+
426
+ if(DEBUG && i % 1000000 == 0)
427
+ {
428
+ printf("Worked lines: %i\n", i);
429
+ }
430
+ // printf("Line: %s", line);
431
+ // COUNTRY,REGION,CITY-NAME,METRO-CODE,CITY-CODE,LATITUDE,LONGITUDE
432
+ country = strtok(line, CITIES_DELIM);
433
+ region = strtok(NULL, CITIES_DELIM);
434
+ name = strtok(NULL, CITIES_DELIM);
435
+ metro_code = strtok(NULL, CITIES_DELIM);
436
+ city_code = strtok(NULL, CITIES_DELIM);
437
+ lat = strtok(NULL, CITIES_DELIM);
438
+ lng = strtok(NULL, CITIES_DELIM);
439
+
440
+ entry = &(db->cities[db->cities_count]);
441
+
442
+ strncpy(entry->country_iso3, country, strlen(country));
443
+
444
+ // entry->country_iso2 = iso2_code(entry->country_iso3);
445
+ strncpy(entry->country_iso2, iso2_code(country), 2);
446
+ strncpy(entry->name, name, strlen(name));
447
+
448
+ entry->city_code = atoi(city_code);
449
+ entry->lat = atof(lat);
450
+ entry->lng = atof(lng);
451
+ db->cities_count++;
452
+ }
453
+ if(DEBUG)
454
+ printf("\n Parsing of %i records needed %.6lf seconds\n", db->cities_count, get_time(&tim)-t1);
455
+ }
456
+
457
+
458
+
459
+ /**
460
+ cache-file is an exact binary copy of the ranges+cities-arrays from memory,
461
+ the layout goes like this:
462
+ db->cities_count [4 Bytes]
463
+ db->ranges_count [4 Bytes]
464
+
465
+ db->cities [sizeof(City)=24 x db->ranges_count Bytes]
466
+ db->ranges [sizeof(IpRange)=24 x db->ranges_count Bytes]
467
+ */
468
+ void
469
+ write_cache_file(IPDB * db){
470
+ struct timeval tim;
471
+ double t1 = get_time(&tim);
472
+ int objects_written;
473
+
474
+ FILE * f;
475
+ f = fopen(db->cache_file_name, "w");
476
+ if(f==NULL){
477
+ if(DEBUG)
478
+ printf("Could not open Cache-File: %s", db->cache_file_name);
479
+ return;
480
+ }
481
+ if(DEBUG){
482
+ printf("Dumping %i records to cache-file: %s\n\n", db->ranges_count, db->cache_file_name);
483
+
484
+ //write the record length at file header
485
+ printf("Writing DB-Header of length: %li\n",sizeof(db->ranges_count));
486
+
487
+ printf("RecordLength: %li\n",sizeof(IpRange));
488
+ printf("FieldLength: %li\n",sizeof(db->ranges[0].from));
489
+ }
490
+ //write the header: i.e.: numbers of records
491
+ fwrite(&(db->cities_count), sizeof(db->cities_count),1,f);
492
+ fwrite(&(db->ranges_count), sizeof(db->ranges_count),1,f);
493
+
494
+ if(DEBUG)
495
+ printf("Writing Contents with %i cities, a %li bytes each, should = %li \n", db->cities_count, sizeof(City), db->cities_count * sizeof(City));
496
+ //write the actual data: all the ranges-array-buffer:
497
+ objects_written = fwrite(db->cities, sizeof(City), db->cities_count, f);
498
+ if(DEBUG)
499
+ printf("Writing Contents with %i ranges, a %li bytes each, should = %li \n", db->ranges_count, sizeof(IpRange), db->ranges_count * sizeof(IpRange));
500
+ //write the actual data: all the ranges-array-buffer:
501
+ objects_written += fwrite(db->ranges, sizeof(IpRange), db->ranges_count, f);
502
+
503
+
504
+ fclose(f);
505
+ if(DEBUG)
506
+ printf("\n Writing CacheFile of %i objects needed %.6lf seconds\n", objects_written, get_time(&tim)-t1);
507
+ }
508
+
509
+ int
510
+ read_cache_file(IPDB * db){
511
+ struct timeval tim;
512
+ double t1 = get_time(&tim);
513
+ FILE * f;
514
+ f = fopen(db->cache_file_name, "r");
515
+ if(f==NULL){
516
+ if(DEBUG)
517
+ printf("Could not open Cache-File: %s", db->cache_file_name);
518
+ return 0;
519
+ }
520
+ int cities_header_read = fread(&(db->cities_count), sizeof(db->cities_count),1,f);
521
+ int ranges_header_read = fread(&(db->ranges_count), sizeof(db->ranges_count),1,f);
522
+
523
+
524
+ if(cities_header_read == 0 || ranges_header_read == 0 || db->cities_count == 0 || db->ranges_count ==0 )
525
+ {
526
+ printf("Could not read Cities-Header from Cache-File: %s", db->cache_file_name);
527
+ return 0;
528
+ }
529
+ if(DEBUG)
530
+ printf("Reading DB-Header from Cache-File: %s, with %i cities and %i ranges\n",db->cache_file_name, db->cities_count, db->ranges_count);
531
+
532
+ int objects_read = 0;
533
+ if(DEBUG)
534
+ printf("Allocating: %lu for cities-array \n", sizeof(City)*(db->cities_count));
535
+ db->cities = malloc(sizeof(City) * db->cities_count);
536
+ objects_read += fread(db->cities, sizeof(City),db->cities_count,f);
537
+ if(DEBUG)
538
+ printf("Allocating: %lu for ranges-array \n", sizeof(IpRange)*(db->ranges_count));
539
+ db->ranges = malloc(sizeof(IpRange) * db->ranges_count);
540
+ objects_read += fread(db->ranges, sizeof(IpRange),db->ranges_count,f);
541
+
542
+
543
+ fclose(f);
544
+ if(DEBUG)
545
+ printf("Reading cacheFile of %i objects needed %.6lf seconds\n", objects_read, get_time(&tim)-t1);
546
+ return objects_read;
547
+ }
548
+
549
+ void
550
+ benchmark_search(IPDB * db,int count){
551
+ printf("(Naiv) benchmark of the City-Search-Function with %i counts \n", count);
552
+ struct timeval tim;
553
+ double t1 = get_time(&tim);
554
+ int i;
555
+ City * city;
556
+
557
+ for(i=0;i<count; i++){
558
+ city = city_by_ip(db, "278.50.47.0");
559
+ }
560
+ double delta = get_time(&tim)-t1;
561
+
562
+ printf("\n\nSearch: %.6lf seconds elapsed, i.e. %.6lf Ops/Second \n", delta, count / delta);
563
+ }
564
+
565
+ IPDB * init_db(char * cities_csv_file, char * ranges_csv_file, char * cache_file_name){
566
+ IPDB *db;
567
+ db = (IPDB*)malloc(sizeof(IPDB));
568
+ if (db == NULL) //no memory left
569
+ return NULL;
570
+ db->cities = NULL;
571
+ db->ranges = NULL;
572
+ db->cache_file_name = cache_file_name;
573
+
574
+ db->cities_csv_file = cities_csv_file;
575
+ db->max_cities_count = MAX_CITIES_COUNT;
576
+ db->ranges_csv_file = ranges_csv_file;
577
+ db->max_ranges_count = MAX_RANGES_COUNT;
578
+
579
+
580
+ if(USE_CACHE && read_cache_file(db) > 0){
581
+ if(DEBUG)
582
+ printf("Loaded DB from Cache-File with %i records \n", db->ranges_count);
583
+ }else{
584
+ if(DEBUG)
585
+ printf("Initializing IPDB from CSV-file: %s \n", db->ranges_csv_file);
586
+ read_cities_csv(db);
587
+ // print_cities(db);
588
+ if(db->cities_count == 0)
589
+ {
590
+ return NULL;
591
+ }
592
+ sort_cities(db);
593
+ read_ranges_csv(db);
594
+ // //TODO: sort ranges
595
+ if(db!=NULL && db->ranges_count > 0 && USE_CACHE)
596
+ {
597
+ if(DEBUG)
598
+ printf("Got %i records from CSV-file, writing to cache...\n", db->ranges_count);
599
+ write_cache_file(db);
600
+ }
601
+ }
602
+ return db;
603
+ }
604
+
data/ext/ipdb.h ADDED
@@ -0,0 +1,69 @@
1
+
2
+ #ifdef INT_2_BYTES
3
+ typedef char int8;
4
+ typedef int int16;
5
+ typedef unsigned int uint16;
6
+ typedef long int32;
7
+ #else
8
+ typedef char int8;
9
+ typedef short int16;
10
+ typedef unsigned short uint16;
11
+ typedef int int32;
12
+ #endif
13
+
14
+
15
+ #define RANGES_DELIM "|"
16
+ #define CITIES_DELIM ","
17
+ #define MAX_CITIES_COUNT 100000 //Usually we have about 50 000 Cities
18
+ #define MAX_RANGES_COUNT 10000000 //Usually we have about 6 Mio IP-Ranges
19
+
20
+
21
+ #define USE_CACHE 1
22
+ #define DEBUG 0
23
+
24
+ typedef struct{
25
+ unsigned long from;
26
+ unsigned long to;
27
+ // uint16 city_code; //city codes are not larger than 2**16 = 65536
28
+ uint16 city_index; //index of the city in the cities-array
29
+ } IpRange;
30
+
31
+ typedef struct{
32
+ uint16 city_code; //city codes are not larger than 2**16 = 65536
33
+ char name[32];
34
+ double lat;
35
+ double lng;
36
+
37
+ char country_iso3[4];
38
+ char country_iso2[3];
39
+ } City;
40
+
41
+ typedef struct{
42
+ char *ranges_csv_file; //the CSV-file with ip-ranges-to-city-code-mappings
43
+ unsigned int max_ranges_count;
44
+ unsigned int ranges_count;
45
+
46
+ char *cities_csv_file;
47
+ unsigned int cities_count;
48
+ unsigned int max_cities_count;
49
+
50
+
51
+ char *cache_file_name; // a binary file to store the whole db.....
52
+ IpRange * ranges;
53
+ City * cities;
54
+
55
+ } IPDB;
56
+
57
+
58
+ // "publicly" visible functions
59
+ IPDB *
60
+ init_db(char * cities_csv_file, char * ranges_csv_file, char * cache_file_name);
61
+
62
+ City *
63
+ city_by_ip(IPDB *db, char *ip);
64
+
65
+ void
66
+ print_city(const City * e);
67
+
68
+ void
69
+ benchmark_search(IPDB * db,int count);
data/ext/test.c ADDED
@@ -0,0 +1,42 @@
1
+ #include "ipdb.h"
2
+
3
+ #include <search.h>
4
+ #include <stdio.h>
5
+ #include <string.h>
6
+ #include <stdlib.h>
7
+ #include <sys/time.h>
8
+
9
+
10
+
11
+ int main() {
12
+ IPDB * db = init_db("data/cities.csv", "data/ip_ranges.csv", "data/ipdb.cache");
13
+ // IPDB * db = init_db(NULL, NULL, NULL);
14
+
15
+
16
+ // City * city;
17
+ //
18
+ // // city = city_by_ip(db, "1.16.0.0");
19
+ // int i;
20
+ int count = 10000000;
21
+
22
+ benchmark_search(db, count);
23
+
24
+ // for(i = 0; i < count; ++i)
25
+ // {
26
+ // if(i % 100000 == 0)
27
+ // printf("Working: i= %i \n", i);
28
+ //
29
+ // city = city_by_ip(db, "91.44.93.35");
30
+ // }
31
+
32
+
33
+
34
+
35
+
36
+ //
37
+ int * response;
38
+ scanf("%i",response);
39
+
40
+ return 0;
41
+
42
+ }
data/geoipdb.gemspec ADDED
@@ -0,0 +1,71 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{geoipdb}
8
+ s.version = "0.1.4"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Eugen Martin"]
12
+ s.date = %q{2011-01-13}
13
+ s.description = %q{Returns a GeoLocation to a given IP. Reads Data from CSV-Files and uses internal binary caching.}
14
+ s.email = %q{eugeniusmartinus@googlemail.com}
15
+ s.extensions = ["ext/extconf.rb"]
16
+ s.extra_rdoc_files = [
17
+ "LICENSE.txt",
18
+ "README.markdown"
19
+ ]
20
+ s.files = [
21
+ ".document",
22
+ ".rspec",
23
+ "Gemfile",
24
+ "LICENSE.txt",
25
+ "README.markdown",
26
+ "Rakefile",
27
+ "VERSION",
28
+ "ext/build.sh",
29
+ "ext/extconf.rb",
30
+ "ext/geoipdb.c",
31
+ "ext/ipdb.c",
32
+ "ext/ipdb.h",
33
+ "ext/test.c",
34
+ "geoipdb.gemspec",
35
+ "lib/geoipdb.rb",
36
+ "spec/geoipdb_spec.rb",
37
+ "spec/spec_helper.rb"
38
+ ]
39
+ s.homepage = %q{http://github.com/olgen/geoipdb}
40
+ s.licenses = ["MIT"]
41
+ s.require_paths = ["ext"]
42
+ s.rubygems_version = %q{1.3.7}
43
+ s.summary = %q{Fast (>3 Mio queries/sec!!!) GeoIpDb implementation for Ruby using C-Extensions.}
44
+ s.test_files = [
45
+ "spec/geoipdb_spec.rb",
46
+ "spec/spec_helper.rb"
47
+ ]
48
+
49
+ if s.respond_to? :specification_version then
50
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
51
+ s.specification_version = 3
52
+
53
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
54
+ s.add_development_dependency(%q<rspec>, ["~> 2.1.0"])
55
+ s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
56
+ s.add_development_dependency(%q<jeweler>, ["~> 1.5.1"])
57
+ s.add_development_dependency(%q<rcov>, [">= 0"])
58
+ else
59
+ s.add_dependency(%q<rspec>, ["~> 2.1.0"])
60
+ s.add_dependency(%q<bundler>, ["~> 1.0.0"])
61
+ s.add_dependency(%q<jeweler>, ["~> 1.5.1"])
62
+ s.add_dependency(%q<rcov>, [">= 0"])
63
+ end
64
+ else
65
+ s.add_dependency(%q<rspec>, ["~> 2.1.0"])
66
+ s.add_dependency(%q<bundler>, ["~> 1.0.0"])
67
+ s.add_dependency(%q<jeweler>, ["~> 1.5.1"])
68
+ s.add_dependency(%q<rcov>, [">= 0"])
69
+ end
70
+ end
71
+
data/lib/geoipdb.rb ADDED
File without changes
@@ -0,0 +1,7 @@
1
+ COUNTRY,REGION,CITY-NAME,METRO-CODE,CITY-CODE,LATITUDE,LONGITUDE
2
+ ***,***,?,0,7,0,0
3
+ afg,?,?,0,1,33,1
4
+ afg,no region,herat,-1,2,34.3452,62.
5
+ afg,no region,kabul,-1,3,34.5167,69.1833
6
+ afg,no region,kandahar,-1,4,31.6111,65.702
7
+ afg,no region,mazar-e sharif,-1,5,36.7041,67.1096
@@ -0,0 +1,8 @@
1
+ COUNTRY,REGION,CITY-NAME,METRO-CODE,CITY-CODE,LATITUDE,LONGITUDE
2
+ ***,***,?,0,7,0,0
3
+ afg,?,?,0,1,33,1
4
+ afg,no region,herat,-1,2,34.3452,62.
5
+
6
+ afg,no , , , , region,kabul,-1,3,34.5167,69.1833
7
+ afg,no region,, , kandahar,-1,4,31.6111,65.702
8
+ afg,no region,maza , ,r-e sharif,-1,5,36.7041,67.1096
@@ -0,0 +1,11 @@
1
+ start_ip|end_ip|field 13|
2
+ 0.0.0.0|0.0.0.255|0|
3
+ 0.0.1.0|0.255.255.255|1|
4
+ 1.0.0.0|1.0.0.255|2|
5
+ 1.0.1.0|1.1.0.255|3|
6
+ 1.1.1.0|1.1.1.255|4|
7
+ 1.1.2.0|1.2.2.255|5|
8
+ 1.2.3.0|1.2.3.255|5|
9
+ 1.2.4.0|1.3.255.255|2|
10
+ 1.4.0.0|1.4.0.255|5|
11
+ 1.4.1.0|1.8.255.255|3|
@@ -0,0 +1,20 @@
1
+
2
+
3
+
4
+ start_ip|end_ip|field 13|
5
+ 0.0.0.0|0.0.0.255|0|
6
+
7
+ 0.0.1.0|0.255.255.255|1|
8
+
9
+ 1.0.0.0|1.0.0.255|2|
10
+
11
+ 1.0.1.0|1.1.0.255|3|
12
+
13
+ asfdasdf asdf asfasdfasdf§$%&/
14
+
15
+ 1, | | 7 |.1.1.0|1.1.1.255|4|
16
+ 1.1.2.0|1.2.2.255|5|
17
+ 1.2.3.0|1.2.3.255|5|
18
+ 1.2.4.0|1.3.255.255|2|
19
+ 1.4.0.0|1.4.0.255|5|
20
+ 1.4.1.0|1.8.255.255|3|
@@ -0,0 +1,37 @@
1
+ require File.expand_path(File.dirname(__FILE__)+'/spec_helper')
2
+
3
+
4
+ describe "Geoipdb" do
5
+ def init_db
6
+ @db = GeoIpDb.init './sample_data/cities.csv', './sample_data/ip_ranges.csv', @cache_file
7
+ end
8
+
9
+ before :each do
10
+ @cache_file = 'sample_data/ipdb.cache'
11
+ end
12
+
13
+ it "should init correctly with sample data and create the cache-file" do
14
+ init_db
15
+ @db.should_not be_nil
16
+ File.exist?(@cache_file).should be_true
17
+ end
18
+
19
+ it "sould find the sample cities correcty" do
20
+ init_db
21
+ #afg,no region,kabul,-1,3,34.5167,69.1833
22
+ @db.city_by_ip("1.1.0.254").should == {'city_code'=>3, 'name'=>'kabul', 'country'=>'af', 'lat'=>34.5167, 'lng'=>69.1833}
23
+ end
24
+
25
+ it "should not throw an exception fault if data is corrupt" do
26
+ @db = GeoIpDb.init './sample_data/cities_corrupt.csv', './sample_data/ip_ranges_corrupt.csv', @cache_file
27
+ end
28
+
29
+ it "should not init a db object if data files are missing" do
30
+ GeoIpDb.init( './sample_data/bla.csv', './sample_data/blubb.csv', @cache_file ).should be_nil
31
+ end
32
+
33
+
34
+ after :each do
35
+ File.unlink @cache_file if File.exist? @cache_file
36
+ end
37
+ end
@@ -0,0 +1,12 @@
1
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'ext'))
2
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
3
+ require 'rspec'
4
+ require 'geoipdb'
5
+
6
+ # Requires supporting files with custom matchers and macros, etc,
7
+ # in ./support/ and its subdirectories.
8
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
9
+
10
+ RSpec.configure do |config|
11
+
12
+ end
metadata ADDED
@@ -0,0 +1,150 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: geoipdb
3
+ version: !ruby/object:Gem::Version
4
+ hash: 19
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 4
10
+ version: 0.1.4
11
+ platform: ruby
12
+ authors:
13
+ - Eugen Martin
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-01-14 00:00:00 +01:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ type: :development
23
+ version_requirements: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ~>
27
+ - !ruby/object:Gem::Version
28
+ hash: 11
29
+ segments:
30
+ - 2
31
+ - 1
32
+ - 0
33
+ version: 2.1.0
34
+ requirement: *id001
35
+ prerelease: false
36
+ name: rspec
37
+ - !ruby/object:Gem::Dependency
38
+ type: :development
39
+ version_requirements: &id002 !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ~>
43
+ - !ruby/object:Gem::Version
44
+ hash: 23
45
+ segments:
46
+ - 1
47
+ - 0
48
+ - 0
49
+ version: 1.0.0
50
+ requirement: *id002
51
+ prerelease: false
52
+ name: bundler
53
+ - !ruby/object:Gem::Dependency
54
+ type: :development
55
+ version_requirements: &id003 !ruby/object:Gem::Requirement
56
+ none: false
57
+ requirements:
58
+ - - ~>
59
+ - !ruby/object:Gem::Version
60
+ hash: 1
61
+ segments:
62
+ - 1
63
+ - 5
64
+ - 1
65
+ version: 1.5.1
66
+ requirement: *id003
67
+ prerelease: false
68
+ name: jeweler
69
+ - !ruby/object:Gem::Dependency
70
+ type: :development
71
+ version_requirements: &id004 !ruby/object:Gem::Requirement
72
+ none: false
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ hash: 3
77
+ segments:
78
+ - 0
79
+ version: "0"
80
+ requirement: *id004
81
+ prerelease: false
82
+ name: rcov
83
+ description: Returns a GeoLocation to a given IP. Reads Data from CSV-Files and uses internal binary caching.
84
+ email: eugeniusmartinus@googlemail.com
85
+ executables: []
86
+
87
+ extensions:
88
+ - ext/extconf.rb
89
+ extra_rdoc_files:
90
+ - LICENSE.txt
91
+ - README.markdown
92
+ files:
93
+ - .document
94
+ - .rspec
95
+ - Gemfile
96
+ - LICENSE.txt
97
+ - README.markdown
98
+ - Rakefile
99
+ - VERSION
100
+ - ext/build.sh
101
+ - ext/extconf.rb
102
+ - ext/geoipdb.c
103
+ - ext/ipdb.c
104
+ - ext/ipdb.h
105
+ - ext/test.c
106
+ - geoipdb.gemspec
107
+ - lib/geoipdb.rb
108
+ - sample_data/cities.csv
109
+ - sample_data/citiess_corrupt.csv
110
+ - sample_data/ip_ranges.csv
111
+ - sample_data/ip_ranges_corrupt.csv
112
+ - spec/geoipdb_spec.rb
113
+ - spec/spec_helper.rb
114
+ has_rdoc: true
115
+ homepage: http://github.com/olgen/geoipdb
116
+ licenses:
117
+ - MIT
118
+ post_install_message:
119
+ rdoc_options: []
120
+
121
+ require_paths:
122
+ - ext
123
+ required_ruby_version: !ruby/object:Gem::Requirement
124
+ none: false
125
+ requirements:
126
+ - - ">="
127
+ - !ruby/object:Gem::Version
128
+ hash: 3
129
+ segments:
130
+ - 0
131
+ version: "0"
132
+ required_rubygems_version: !ruby/object:Gem::Requirement
133
+ none: false
134
+ requirements:
135
+ - - ">="
136
+ - !ruby/object:Gem::Version
137
+ hash: 3
138
+ segments:
139
+ - 0
140
+ version: "0"
141
+ requirements: []
142
+
143
+ rubyforge_project:
144
+ rubygems_version: 1.4.2
145
+ signing_key:
146
+ specification_version: 3
147
+ summary: Fast (>3 Mio queries/sec!!!) GeoIpDb implementation for Ruby using C-Extensions.
148
+ test_files:
149
+ - spec/geoipdb_spec.rb
150
+ - spec/spec_helper.rb