geoipdb 0.1.4

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.
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