diff --git a/lib/generators/geocoder/maxmind/geolite_city_generator.rb b/lib/generators/geocoder/maxmind/geolite_city_generator.rb new file mode 100644 index 0000000000000000000000000000000000000000..2743957a48206d6ceae072810e6b1ae03ab1de70 --- /dev/null +++ b/lib/generators/geocoder/maxmind/geolite_city_generator.rb @@ -0,0 +1,28 @@ +require 'rails/generators/migration' + +module Geocoder + module Generators + module Maxmind + class GeoliteCityGenerator < Rails::Generators::Base + include Rails::Generators::Migration + + source_root File.expand_path('../templates', __FILE__) + + def copy_migration_files + migration_template "migration/geolite_city.rb", "db/migrate/geocoder_maxmind_geolite_city.rb" + end + + # Define the next_migration_number method (necessary for the + # migration_template method to work) + def self.next_migration_number(dirname) + if ActiveRecord::Base.timestamped_migrations + sleep 1 # make sure each time we get a different timestamp + Time.new.utc.strftime("%Y%m%d%H%M%S") + else + "%.3d" % (current_migration_number(dirname) + 1) + end + end + end + end + end +end diff --git a/lib/generators/geocoder/maxmind/geolite_country_generator.rb b/lib/generators/geocoder/maxmind/geolite_country_generator.rb new file mode 100644 index 0000000000000000000000000000000000000000..c9e4bc9560f31e9bae357e9ebb8b568ac12e9e64 --- /dev/null +++ b/lib/generators/geocoder/maxmind/geolite_country_generator.rb @@ -0,0 +1,28 @@ +require 'rails/generators/migration' + +module Geocoder + module Generators + module Maxmind + class GeoliteCountryGenerator < Rails::Generators::Base + include Rails::Generators::Migration + + source_root File.expand_path('../templates', __FILE__) + + def copy_migration_files + migration_template "migration/geolite_country.rb", "db/migrate/geocoder_maxmind_geolite_country.rb" + end + + # Define the next_migration_number method (necessary for the + # migration_template method to work) + def self.next_migration_number(dirname) + if ActiveRecord::Base.timestamped_migrations + sleep 1 # make sure each time we get a different timestamp + Time.new.utc.strftime("%Y%m%d%H%M%S") + else + "%.3d" % (current_migration_number(dirname) + 1) + end + end + end + end + end +end diff --git a/lib/generators/geocoder/templates/migration/maxmind_geolite_city.rb b/lib/generators/geocoder/maxmind/templates/migration/geolite_city.rb similarity index 85% rename from lib/generators/geocoder/templates/migration/maxmind_geolite_city.rb rename to lib/generators/geocoder/maxmind/templates/migration/geolite_city.rb index 3904d9d76733bc727609a3ddbfa89a53223e1e27..d5e45f976a3d5f7b3aef2a2593d8091855fadfa3 100644 --- a/lib/generators/geocoder/templates/migration/maxmind_geolite_city.rb +++ b/lib/generators/geocoder/maxmind/templates/migration/geolite_city.rb @@ -1,5 +1,5 @@ class GeocoderMaxmindGeoliteCity < ActiveRecord::Migration - def change + def self.up create_table :maxmind_geolite_city_blocks, id: false do |t| t.column :startIpNum, 'integer unsigned', null: false t.column :endIpNum, 'integer unsigned', null: false @@ -8,7 +8,7 @@ class GeocoderMaxmindGeoliteCity < ActiveRecord::Migration add_index :maxmind_geolite_city_blocks, :startIpNum, unique: true create_table :maxmind_geolite_city_location, id: false do |t| - t.column :locId, 'integer unsigned', null: false + t.column :locId, 'integer unsigned', null: false t.string :country, null: false t.string :region, null: false t.string :city @@ -20,4 +20,8 @@ class GeocoderMaxmindGeoliteCity < ActiveRecord::Migration end add_index :maxmind_geolite_city_location, :locId, unique: true end + + def self.down + drop_table :maxmind_geolite_city + end end diff --git a/lib/generators/geocoder/maxmind/templates/migration/geolite_country.rb b/lib/generators/geocoder/maxmind/templates/migration/geolite_country.rb new file mode 100644 index 0000000000000000000000000000000000000000..d47d46b857b31bdf29c6995eac968e330267a071 --- /dev/null +++ b/lib/generators/geocoder/maxmind/templates/migration/geolite_country.rb @@ -0,0 +1,17 @@ +class GeocoderMaxmindGeoliteCountry < ActiveRecord::Migration + def self.up + create_table :maxmind_geolite_country, id: false do |t| + t.column :startIp, :string + t.column :endIp, :string + t.column :startIpNum, 'integer unsigned', null: false + t.column :endIpNum, 'integer unsigned', null: false + t.column :country_code, :string, null: false + t.column :country, :string, null: false + end + add_index :maxmind_geolite_country, :startIpNum, unique: true + end + + def self.down + drop_table :maxmind_geolite_country + end +end diff --git a/lib/generators/geocoder/maxmind_generator.rb b/lib/generators/geocoder/maxmind_generator.rb deleted file mode 100644 index 40921ab1269045b0b34c5bd39cbf75a4c85c7d7c..0000000000000000000000000000000000000000 --- a/lib/generators/geocoder/maxmind_generator.rb +++ /dev/null @@ -1,26 +0,0 @@ -require 'rails/generators/migration' - -module Geocoder - module Generators - class MaxmindGenerator < Rails::Generators::Base - include Rails::Generators::Migration - - source_root File.expand_path('../templates', __FILE__) - - def copy_migration_files - migration_template "migration/maxmind_geolite_city.rb", "db/migrate/geocoder_maxmind_geolite_city.rb" - end - - # Define the next_migration_number method (necessary for the - # migration_template method to work) - def self.next_migration_number(dirname) - if ActiveRecord::Base.timestamped_migrations - sleep 1 # make sure each time we get a different timestamp - Time.new.utc.strftime("%Y%m%d%H%M%S") - else - "%.3d" % (current_migration_number(dirname) + 1) - end - end - end - end -end \ No newline at end of file diff --git a/lib/geocoder/lookups/maxmind_local.rb b/lib/geocoder/lookups/maxmind_local.rb index 7ba53b05cc3b734bee20cd730b77ed06a5678f89..8a389e3dc58d18c38b57212f3c226bf0abab6765 100644 --- a/lib/geocoder/lookups/maxmind_local.rb +++ b/lib/geocoder/lookups/maxmind_local.rb @@ -32,7 +32,7 @@ module Geocoder::Lookup geoip_class = RUBY_PLATFORM == "java" ? JGeoIP : GeoIP result = geoip_class.new(configuration[:file]).city(query.to_s) result.nil? ? [] : [result.to_hash] - else + elsif configuration[:package] == :city addr = IPAddr.new(query.text).to_i q = "SELECT l.country, l.region, l.city FROM maxmind_geolite_city_location l JOIN maxmind_geolite_city_blocks b USING (locId) @@ -40,6 +40,14 @@ module Geocoder::Lookup if r = ActiveRecord::Base.connection.execute(q).first [Hash[*[:country_name, :region_name, :city_name].zip(r).flatten]] end + elsif configuration[:package] == :country + addr = IPAddr.new(query.text).to_i + q = "SELECT country, country_code + FROM maxmind_geolite_country + WHERE startIpNum <= #{addr} AND #{addr} <= endIpNum" + if r = ActiveRecord::Base.connection.execute(q).first + [Hash[*[:country_name, :country_code].zip(r).flatten]] + end end end end diff --git a/lib/geocoder/results/maxmind_local.rb b/lib/geocoder/results/maxmind_local.rb index 370bf92fa18fe2317ebec23b4d30c5c72aa174bf..6f3f9c4354599c9c1ffb174eeeb90fb683c45b5d 100644 --- a/lib/geocoder/results/maxmind_local.rb +++ b/lib/geocoder/results/maxmind_local.rb @@ -29,7 +29,7 @@ module Geocoder::Result end def country_code - @data[:country_code3] + @data[:country_code] end def postal_code diff --git a/lib/maxmind_database.rb b/lib/maxmind_database.rb index efcbe56e2f1d90eb3df6a5dda0f030607b4932e8..f223b6b445ae59d1315ef40e2f12921719c15f1c 100644 --- a/lib/maxmind_database.rb +++ b/lib/maxmind_database.rb @@ -22,32 +22,10 @@ module Geocoder def insert(package, dir = "tmp") data_files(package).each do |filepath,table| - # delete from table print "Resetting table #{table}..." ActiveRecord::Base.connection.execute("DELETE FROM #{table}") puts "done" - # insert into table - start_time = Time.now - print "Loading data for table #{table}" - rows = [] - headers = nil - CSV.foreach(filepath, encoding: "ISO-8859-1") do |line| - if line.first[0...9] == "Copyright" - next - elsif headers.nil? - headers = line - next - else - rows << line.to_a - if rows.size == 10000 - insert_into_table(table, headers, rows) - rows = [] - print "." - end - end - end - insert_into_table(table, headers, rows) if rows.size > 0 - puts "done (#{Time.now - start_time} seconds)" + insert_into_table(table, filepath) end end @@ -59,7 +37,35 @@ module Geocoder private # ------------------------------------------------------------- - def insert_into_table(table, headers, rows) + def insert_into_table(table, filepath) + start_time = Time.now + print "Loading data for table #{table}" + rows = [] + if table =~ /city/ + headers = nil + else + headers = %w[startIp endIp startIpNum endIpNum country_code country] + end + CSV.foreach(filepath, encoding: "ISO-8859-1") do |line| + if line.first[0...9] == "Copyright" + next + elsif headers.nil? + headers = line + next + else + rows << line.to_a + if rows.size == 10000 + insert_rows(table, headers, rows) + rows = [] + print "." + end + end + end + insert_rows(table, headers, rows) if rows.size > 0 + puts "done (#{Time.now - start_time} seconds)" + end + + def insert_rows(table, headers, rows) value_strings = rows.map do |row| "(" + row.map{ |col| sql_escaped_value(col) }.join(',') + ")" end @@ -79,6 +85,8 @@ module Geocoder # use the last two in case multiple versions exist files = Dir.glob(File.join(dir, "GeoLiteCity_*/*.csv"))[-2..-1] Hash[*files.zip(["maxmind_geolite_city_blocks", "maxmind_geolite_city_location"]).flatten] + when :geolite_country_csv + {File.join(dir, "GeoIPCountryWhois.csv") => "maxmind_geolite_country"} end end diff --git a/lib/tasks/maxmind.rake b/lib/tasks/maxmind.rake index 0de813a76796f751b434b2344add83a2166025d2..1bcfcb50072231ddaf36008ff8f7851f0f98f502 100644 --- a/lib/tasks/maxmind.rake +++ b/lib/tasks/maxmind.rake @@ -2,44 +2,68 @@ require 'maxmind_database' namespace :geocoder do namespace :maxmind do - namespace :geolite_city do + namespace :geolite do desc "Download and load/refresh MaxMind GeoLite City data" task load: [:download, :extract, :insert] desc "Download MaxMind GeoLite City data" task :download do - dir = ENV['DIR'] || "tmp/" - Geocoder::MaxmindDatabase.download(:geolite_city_csv, dir) + p = check_for_package! + download(p, dir: ENV['DIR'] || "tmp/") end desc "Extract (unzip) MaxMind GeoLite City data" task :extract do - begin - require 'zip' - rescue LoadError - puts "Please install gem: rubyzip (>= 1.0.0)" - exit - end - require 'fileutils' - dir = ENV['DIR'] || "tmp/" - archive_filename = Geocoder::MaxmindDatabase.archive_filename(:geolite_city_csv) - Zip::File.open(File.join(dir, archive_filename)).each do |entry| - filepath = File.join(dir, entry.name) - if File.exist? filepath - warn "File already exists (#{entry.name}), skipping" - else - FileUtils.mkdir_p(File.dirname(filepath)) - entry.extract(filepath) - end - end + p = check_for_package! + extract(p, dir: ENV['DIR'] || "tmp/") end desc "Load/refresh MaxMind GeoLite City data" task insert: [:environment] do - dir = ENV['DIR'] || "tmp/" - Geocoder::MaxmindDatabase.insert(:geolite_city_csv, dir) + p = check_for_package! + insert(p, dir: ENV['DIR'] || "tmp/") end end end end + +def check_for_package! + if %w[city country].include?(p = ENV['PACKAGE']) + return p + else + puts "Please specify PACKAGE=city or PACKAGE=country" + exit + end +end + +def download(package, options = {}) + p = "geolite_#{package}_csv".intern + Geocoder::MaxmindDatabase.download(p, options[:dir]) +end + +def extract(package, options = {}) + begin + require 'zip' + rescue LoadError + puts "Please install gem: rubyzip (>= 1.0.0)" + exit + end + require 'fileutils' + p = "geolite_#{package}_csv".intern + archive_filename = Geocoder::MaxmindDatabase.archive_filename(p) + Zip::File.open(File.join(options[:dir], archive_filename)).each do |entry| + filepath = File.join(options[:dir], entry.name) + if File.exist? filepath + warn "File already exists (#{entry.name}), skipping" + else + FileUtils.mkdir_p(File.dirname(filepath)) + entry.extract(filepath) + end + end +end + +def insert(package, options = {}) + p = "geolite_#{package}_csv".intern + Geocoder::MaxmindDatabase.insert(p, options[:dir]) +end