broken-geocoder 1.3.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.
Files changed (119) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +467 -0
  3. data/LICENSE +20 -0
  4. data/README.md +1193 -0
  5. data/bin/geocode +5 -0
  6. data/examples/autoexpire_cache_dalli.rb +62 -0
  7. data/examples/autoexpire_cache_redis.rb +28 -0
  8. data/examples/cache_bypass.rb +48 -0
  9. data/examples/reverse_geocode_job.rb +40 -0
  10. data/lib/generators/geocoder/config/config_generator.rb +14 -0
  11. data/lib/generators/geocoder/config/templates/initializer.rb +21 -0
  12. data/lib/generators/geocoder/maxmind/geolite_city_generator.rb +28 -0
  13. data/lib/generators/geocoder/maxmind/geolite_country_generator.rb +28 -0
  14. data/lib/generators/geocoder/maxmind/templates/migration/geolite_city.rb +30 -0
  15. data/lib/generators/geocoder/maxmind/templates/migration/geolite_country.rb +17 -0
  16. data/lib/geocoder.rb +48 -0
  17. data/lib/geocoder/cache.rb +90 -0
  18. data/lib/geocoder/calculations.rb +431 -0
  19. data/lib/geocoder/cli.rb +121 -0
  20. data/lib/geocoder/configuration.rb +129 -0
  21. data/lib/geocoder/configuration_hash.rb +11 -0
  22. data/lib/geocoder/esri_token.rb +38 -0
  23. data/lib/geocoder/exceptions.rb +37 -0
  24. data/lib/geocoder/ip_address.rb +13 -0
  25. data/lib/geocoder/kernel_logger.rb +25 -0
  26. data/lib/geocoder/logger.rb +47 -0
  27. data/lib/geocoder/lookup.rb +110 -0
  28. data/lib/geocoder/lookups/baidu.rb +59 -0
  29. data/lib/geocoder/lookups/baidu_ip.rb +59 -0
  30. data/lib/geocoder/lookups/base.rb +325 -0
  31. data/lib/geocoder/lookups/bing.rb +80 -0
  32. data/lib/geocoder/lookups/dstk.rb +20 -0
  33. data/lib/geocoder/lookups/esri.rb +64 -0
  34. data/lib/geocoder/lookups/freegeoip.rb +51 -0
  35. data/lib/geocoder/lookups/geocoder_ca.rb +53 -0
  36. data/lib/geocoder/lookups/geocoder_us.rb +43 -0
  37. data/lib/geocoder/lookups/geocodio.rb +42 -0
  38. data/lib/geocoder/lookups/geoip2.rb +45 -0
  39. data/lib/geocoder/lookups/geoportail_lu.rb +65 -0
  40. data/lib/geocoder/lookups/google.rb +91 -0
  41. data/lib/geocoder/lookups/google_places_details.rb +50 -0
  42. data/lib/geocoder/lookups/google_premier.rb +47 -0
  43. data/lib/geocoder/lookups/here.rb +62 -0
  44. data/lib/geocoder/lookups/ipapi_com.rb +86 -0
  45. data/lib/geocoder/lookups/ipinfo_io.rb +55 -0
  46. data/lib/geocoder/lookups/latlon.rb +59 -0
  47. data/lib/geocoder/lookups/mapbox.rb +53 -0
  48. data/lib/geocoder/lookups/mapquest.rb +59 -0
  49. data/lib/geocoder/lookups/mapzen.rb +15 -0
  50. data/lib/geocoder/lookups/maxmind.rb +90 -0
  51. data/lib/geocoder/lookups/maxmind_geoip2.rb +69 -0
  52. data/lib/geocoder/lookups/maxmind_local.rb +65 -0
  53. data/lib/geocoder/lookups/nominatim.rb +52 -0
  54. data/lib/geocoder/lookups/okf.rb +44 -0
  55. data/lib/geocoder/lookups/opencagedata.rb +58 -0
  56. data/lib/geocoder/lookups/ovi.rb +62 -0
  57. data/lib/geocoder/lookups/pelias.rb +64 -0
  58. data/lib/geocoder/lookups/pointpin.rb +68 -0
  59. data/lib/geocoder/lookups/postcode_anywhere_uk.rb +51 -0
  60. data/lib/geocoder/lookups/smarty_streets.rb +50 -0
  61. data/lib/geocoder/lookups/telize.rb +55 -0
  62. data/lib/geocoder/lookups/test.rb +44 -0
  63. data/lib/geocoder/lookups/yandex.rb +58 -0
  64. data/lib/geocoder/models/active_record.rb +50 -0
  65. data/lib/geocoder/models/base.rb +39 -0
  66. data/lib/geocoder/models/mongo_base.rb +62 -0
  67. data/lib/geocoder/models/mongo_mapper.rb +26 -0
  68. data/lib/geocoder/models/mongoid.rb +32 -0
  69. data/lib/geocoder/query.rb +111 -0
  70. data/lib/geocoder/railtie.rb +26 -0
  71. data/lib/geocoder/request.rb +83 -0
  72. data/lib/geocoder/results/baidu.rb +79 -0
  73. data/lib/geocoder/results/baidu_ip.rb +62 -0
  74. data/lib/geocoder/results/base.rb +67 -0
  75. data/lib/geocoder/results/bing.rb +52 -0
  76. data/lib/geocoder/results/dstk.rb +6 -0
  77. data/lib/geocoder/results/esri.rb +75 -0
  78. data/lib/geocoder/results/freegeoip.rb +45 -0
  79. data/lib/geocoder/results/geocoder_ca.rb +60 -0
  80. data/lib/geocoder/results/geocoder_us.rb +39 -0
  81. data/lib/geocoder/results/geocodio.rb +70 -0
  82. data/lib/geocoder/results/geoip2.rb +62 -0
  83. data/lib/geocoder/results/geoportail_lu.rb +69 -0
  84. data/lib/geocoder/results/google.rb +139 -0
  85. data/lib/geocoder/results/google_places_details.rb +35 -0
  86. data/lib/geocoder/results/google_premier.rb +6 -0
  87. data/lib/geocoder/results/here.rb +71 -0
  88. data/lib/geocoder/results/ipapi_com.rb +45 -0
  89. data/lib/geocoder/results/ipinfo_io.rb +48 -0
  90. data/lib/geocoder/results/latlon.rb +71 -0
  91. data/lib/geocoder/results/mapbox.rb +47 -0
  92. data/lib/geocoder/results/mapquest.rb +48 -0
  93. data/lib/geocoder/results/mapzen.rb +5 -0
  94. data/lib/geocoder/results/maxmind.rb +135 -0
  95. data/lib/geocoder/results/maxmind_geoip2.rb +9 -0
  96. data/lib/geocoder/results/maxmind_local.rb +49 -0
  97. data/lib/geocoder/results/nominatim.rb +99 -0
  98. data/lib/geocoder/results/okf.rb +106 -0
  99. data/lib/geocoder/results/opencagedata.rb +90 -0
  100. data/lib/geocoder/results/ovi.rb +71 -0
  101. data/lib/geocoder/results/pelias.rb +58 -0
  102. data/lib/geocoder/results/pointpin.rb +40 -0
  103. data/lib/geocoder/results/postcode_anywhere_uk.rb +42 -0
  104. data/lib/geocoder/results/smarty_streets.rb +106 -0
  105. data/lib/geocoder/results/telize.rb +45 -0
  106. data/lib/geocoder/results/test.rb +33 -0
  107. data/lib/geocoder/results/yandex.rb +92 -0
  108. data/lib/geocoder/sql.rb +107 -0
  109. data/lib/geocoder/stores/active_record.rb +305 -0
  110. data/lib/geocoder/stores/base.rb +116 -0
  111. data/lib/geocoder/stores/mongo_base.rb +58 -0
  112. data/lib/geocoder/stores/mongo_mapper.rb +13 -0
  113. data/lib/geocoder/stores/mongoid.rb +13 -0
  114. data/lib/geocoder/version.rb +3 -0
  115. data/lib/hash_recursive_merge.rb +74 -0
  116. data/lib/maxmind_database.rb +109 -0
  117. data/lib/tasks/geocoder.rake +38 -0
  118. data/lib/tasks/maxmind.rake +73 -0
  119. metadata +167 -0
@@ -0,0 +1,116 @@
1
+ module Geocoder
2
+ module Store
3
+ module Base
4
+
5
+ ##
6
+ # Is this object geocoded? (Does it have latitude and longitude?)
7
+ #
8
+ def geocoded?
9
+ to_coordinates.compact.size > 0
10
+ end
11
+
12
+ ##
13
+ # Coordinates [lat,lon] of the object.
14
+ #
15
+ def to_coordinates
16
+ [:latitude, :longitude].map{ |i| send self.class.geocoder_options[i] }
17
+ end
18
+
19
+ ##
20
+ # Calculate the distance from the object to an arbitrary point.
21
+ # See Geocoder::Calculations.distance_between for ways of specifying
22
+ # the point. Also takes a symbol specifying the units
23
+ # (:mi or :km; can be specified in Geocoder configuration).
24
+ #
25
+ def distance_to(point, units = nil)
26
+ units ||= self.class.geocoder_options[:units]
27
+ return nil unless geocoded?
28
+ Geocoder::Calculations.distance_between(
29
+ to_coordinates, point, :units => units)
30
+ end
31
+
32
+ alias_method :distance_from, :distance_to
33
+
34
+ ##
35
+ # Calculate the bearing from the object to another point.
36
+ # See Geocoder::Calculations.distance_between for
37
+ # ways of specifying the point.
38
+ #
39
+ def bearing_to(point, options = {})
40
+ options[:method] ||= self.class.geocoder_options[:method]
41
+ return nil unless geocoded?
42
+ Geocoder::Calculations.bearing_between(
43
+ to_coordinates, point, options)
44
+ end
45
+
46
+ ##
47
+ # Calculate the bearing from another point to the object.
48
+ # See Geocoder::Calculations.distance_between for
49
+ # ways of specifying the point.
50
+ #
51
+ def bearing_from(point, options = {})
52
+ options[:method] ||= self.class.geocoder_options[:method]
53
+ return nil unless geocoded?
54
+ Geocoder::Calculations.bearing_between(
55
+ point, to_coordinates, options)
56
+ end
57
+
58
+ ##
59
+ # Look up coordinates and assign to +latitude+ and +longitude+ attributes
60
+ # (or other as specified in +geocoded_by+). Returns coordinates (array).
61
+ #
62
+ def geocode
63
+ fail
64
+ end
65
+
66
+ ##
67
+ # Look up address and assign to +address+ attribute (or other as specified
68
+ # in +reverse_geocoded_by+). Returns address (string).
69
+ #
70
+ def reverse_geocode
71
+ fail
72
+ end
73
+
74
+ private # --------------------------------------------------------------
75
+
76
+ ##
77
+ # Look up geographic data based on object attributes (configured in
78
+ # geocoded_by or reverse_geocoded_by) and handle the results with the
79
+ # block (given to geocoded_by or reverse_geocoded_by). The block is
80
+ # given two-arguments: the object being geocoded and an array of
81
+ # Geocoder::Result objects).
82
+ #
83
+ def do_lookup(reverse = false)
84
+ options = self.class.geocoder_options
85
+ if reverse and options[:reverse_geocode]
86
+ query = to_coordinates
87
+ elsif !reverse and options[:geocode]
88
+ query = send(options[:user_address])
89
+ else
90
+ return
91
+ end
92
+
93
+ query_options = [:lookup, :ip_lookup, :language].inject({}) do |hash, key|
94
+ if options.has_key?(key)
95
+ val = options[key]
96
+ hash[key] = val.respond_to?(:call) ? val.call(self) : val
97
+ end
98
+ hash
99
+ end
100
+ results = Geocoder.search(query, query_options)
101
+
102
+ # execute custom block, if specified in configuration
103
+ block_key = reverse ? :reverse_block : :geocode_block
104
+ if custom_block = options[block_key]
105
+ custom_block.call(self, results)
106
+
107
+ # else execute block passed directly to this method,
108
+ # which generally performs the "auto-assigns"
109
+ elsif block_given?
110
+ yield(self, results)
111
+ end
112
+ end
113
+ end
114
+ end
115
+ end
116
+
@@ -0,0 +1,58 @@
1
+ module Geocoder::Store
2
+ module MongoBase
3
+
4
+ def self.included_by_model(base)
5
+ base.class_eval do
6
+
7
+ scope :geocoded, lambda {
8
+ where(geocoder_options[:coordinates].ne => nil)
9
+ }
10
+
11
+ scope :not_geocoded, lambda {
12
+ where(geocoder_options[:coordinates] => nil)
13
+ }
14
+ end
15
+ end
16
+
17
+ ##
18
+ # Coordinates [lat,lon] of the object.
19
+ # This method always returns coordinates in lat,lon order,
20
+ # even though internally they are stored in the opposite order.
21
+ #
22
+ def to_coordinates
23
+ coords = send(self.class.geocoder_options[:coordinates])
24
+ coords.is_a?(Array) ? coords.reverse : []
25
+ end
26
+
27
+ ##
28
+ # Look up coordinates and assign to +latitude+ and +longitude+ attributes
29
+ # (or other as specified in +geocoded_by+). Returns coordinates (array).
30
+ #
31
+ def geocode
32
+ do_lookup(false) do |o,rs|
33
+ if r = rs.first
34
+ unless r.coordinates.nil?
35
+ o.__send__ "#{self.class.geocoder_options[:coordinates]}=", r.coordinates.reverse
36
+ end
37
+ r.coordinates
38
+ end
39
+ end
40
+ end
41
+
42
+ ##
43
+ # Look up address and assign to +address+ attribute (or other as specified
44
+ # in +reverse_geocoded_by+). Returns address (string).
45
+ #
46
+ def reverse_geocode
47
+ do_lookup(true) do |o,rs|
48
+ if r = rs.first
49
+ unless r.address.nil?
50
+ o.__send__ "#{self.class.geocoder_options[:fetched_address]}=", r.address
51
+ end
52
+ r.address
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
58
+
@@ -0,0 +1,13 @@
1
+ require 'geocoder/stores/base'
2
+ require 'geocoder/stores/mongo_base'
3
+
4
+ module Geocoder::Store
5
+ module MongoMapper
6
+ include Base
7
+ include MongoBase
8
+
9
+ def self.included(base)
10
+ MongoBase.included_by_model(base)
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ require 'geocoder/stores/base'
2
+ require 'geocoder/stores/mongo_base'
3
+
4
+ module Geocoder::Store
5
+ module Mongoid
6
+ include Base
7
+ include MongoBase
8
+
9
+ def self.included(base)
10
+ MongoBase.included_by_model(base)
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,3 @@
1
+ module Geocoder
2
+ VERSION = "1.3.4"
3
+ end
@@ -0,0 +1,74 @@
1
+ #
2
+ # = Hash Recursive Merge
3
+ #
4
+ # Merges a Ruby Hash recursively, Also known as deep merge.
5
+ # Recursive version of Hash#merge and Hash#merge!.
6
+ #
7
+ # Category:: Ruby
8
+ # Package:: Hash
9
+ # Author:: Simone Carletti <weppos@weppos.net>
10
+ # Copyright:: 2007-2008 The Authors
11
+ # License:: MIT License
12
+ # Link:: http://www.simonecarletti.com/
13
+ # Source:: http://gist.github.com/gists/6391/
14
+ #
15
+ module HashRecursiveMerge
16
+
17
+ #
18
+ # Recursive version of Hash#merge!
19
+ #
20
+ # Adds the contents of +other_hash+ to +hsh+,
21
+ # merging entries in +hsh+ with duplicate keys with those from +other_hash+.
22
+ #
23
+ # Compared with Hash#merge!, this method supports nested hashes.
24
+ # When both +hsh+ and +other_hash+ contains an entry with the same key,
25
+ # it merges and returns the values from both arrays.
26
+ #
27
+ # h1 = {"a" => 100, "b" => 200, "c" => {"c1" => 12, "c2" => 14}}
28
+ # h2 = {"b" => 254, "c" => {"c1" => 16, "c3" => 94}}
29
+ # h1.rmerge!(h2) #=> {"a" => 100, "b" => 254, "c" => {"c1" => 16, "c2" => 14, "c3" => 94}}
30
+ #
31
+ # Simply using Hash#merge! would return
32
+ #
33
+ # h1.merge!(h2) #=> {"a" => 100, "b" = >254, "c" => {"c1" => 16, "c3" => 94}}
34
+ #
35
+ def rmerge!(other_hash)
36
+ merge!(other_hash) do |key, oldval, newval|
37
+ oldval.class == self.class ? oldval.rmerge!(newval) : newval
38
+ end
39
+ end
40
+
41
+ #
42
+ # Recursive version of Hash#merge
43
+ #
44
+ # Compared with Hash#merge!, this method supports nested hashes.
45
+ # When both +hsh+ and +other_hash+ contains an entry with the same key,
46
+ # it merges and returns the values from both arrays.
47
+ #
48
+ # Compared with Hash#merge, this method provides a different approch
49
+ # for merging nasted hashes.
50
+ # If the value of a given key is an Hash and both +other_hash+ abd +hsh
51
+ # includes the same key, the value is merged instead replaced with
52
+ # +other_hash+ value.
53
+ #
54
+ # h1 = {"a" => 100, "b" => 200, "c" => {"c1" => 12, "c2" => 14}}
55
+ # h2 = {"b" => 254, "c" => {"c1" => 16, "c3" => 94}}
56
+ # h1.rmerge(h2) #=> {"a" => 100, "b" => 254, "c" => {"c1" => 16, "c2" => 14, "c3" => 94}}
57
+ #
58
+ # Simply using Hash#merge would return
59
+ #
60
+ # h1.merge(h2) #=> {"a" => 100, "b" = >254, "c" => {"c1" => 16, "c3" => 94}}
61
+ #
62
+ def rmerge(other_hash)
63
+ r = {}
64
+ merge(other_hash) do |key, oldval, newval|
65
+ r[key] = oldval.class == self.class ? oldval.rmerge(newval) : newval
66
+ end
67
+ end
68
+
69
+ end
70
+
71
+
72
+ class Hash
73
+ include HashRecursiveMerge
74
+ end
@@ -0,0 +1,109 @@
1
+ require 'csv'
2
+ require 'net/http'
3
+
4
+ module Geocoder
5
+ module MaxmindDatabase
6
+ extend self
7
+
8
+ def download(package, dir = "tmp")
9
+ filepath = File.expand_path(File.join(dir, archive_filename(package)))
10
+ open(filepath, 'wb') do |file|
11
+ uri = URI.parse(archive_url(package))
12
+ Net::HTTP.start(uri.host, uri.port) do |http|
13
+ http.request_get(uri.path) do |resp|
14
+ # TODO: show progress
15
+ resp.read_body do |segment|
16
+ file.write(segment)
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+
23
+ def insert(package, dir = "tmp")
24
+ data_files(package, dir).each do |filepath,table|
25
+ print "Resetting table #{table}..."
26
+ ActiveRecord::Base.connection.execute("DELETE FROM #{table}")
27
+ puts "done"
28
+ insert_into_table(table, filepath)
29
+ end
30
+ end
31
+
32
+ def archive_filename(package)
33
+ p = archive_url_path(package)
34
+ s = !(pos = p.rindex('/')).nil? && pos + 1 || 0
35
+ p[s..-1]
36
+ end
37
+
38
+ private # -------------------------------------------------------------
39
+
40
+ def table_columns(table_name)
41
+ {
42
+ maxmind_geolite_city_blocks: %w[start_ip_num end_ip_num loc_id],
43
+ maxmind_geolite_city_location: %w[loc_id country region city postal_code latitude longitude metro_code area_code],
44
+ maxmind_geolite_country: %w[start_ip end_ip start_ip_num end_ip_num country_code country]
45
+ }[table_name.to_sym]
46
+ end
47
+
48
+ def insert_into_table(table, filepath)
49
+ start_time = Time.now
50
+ print "Loading data for table #{table}"
51
+ rows = []
52
+ columns = table_columns(table)
53
+ CSV.foreach(filepath, encoding: "ISO-8859-1") do |line|
54
+ # Some files have header rows.
55
+ # skip if starts with "Copyright" or "locId" or "startIpNum"
56
+ next if line.first.match(/[A-z]/)
57
+ rows << line.to_a
58
+ if rows.size == 10000
59
+ insert_rows(table, columns, rows)
60
+ rows = []
61
+ print "."
62
+ end
63
+ end
64
+ insert_rows(table, columns, rows) if rows.size > 0
65
+ puts "done (#{Time.now - start_time} seconds)"
66
+ end
67
+
68
+ def insert_rows(table, headers, rows)
69
+ value_strings = rows.map do |row|
70
+ "(" + row.map{ |col| sql_escaped_value(col) }.join(',') + ")"
71
+ end
72
+ q = "INSERT INTO #{table} (#{headers.join(',')}) " +
73
+ "VALUES #{value_strings.join(',')}"
74
+ ActiveRecord::Base.connection.execute(q)
75
+ end
76
+
77
+ def sql_escaped_value(value)
78
+ value.to_i.to_s == value ? value :
79
+ ActiveRecord::Base.connection.quote(value)
80
+ end
81
+
82
+ def data_files(package, dir = "tmp")
83
+ case package
84
+ when :geolite_city_csv
85
+ # use the last two in case multiple versions exist
86
+ files = Dir.glob(File.join(dir, "GeoLiteCity_*/*.csv"))[-2..-1].sort
87
+ Hash[*files.zip(["maxmind_geolite_city_blocks", "maxmind_geolite_city_location"]).flatten]
88
+ when :geolite_country_csv
89
+ {File.join(dir, "GeoIPCountryWhois.csv") => "maxmind_geolite_country"}
90
+ end
91
+ end
92
+
93
+ def archive_url(package)
94
+ base_url + archive_url_path(package)
95
+ end
96
+
97
+ def archive_url_path(package)
98
+ {
99
+ geolite_country_csv: "GeoIPCountryCSV.zip",
100
+ geolite_city_csv: "GeoLiteCity_CSV/GeoLiteCity-latest.zip",
101
+ geolite_asn_csv: "asnum/GeoIPASNum2.zip"
102
+ }[package]
103
+ end
104
+
105
+ def base_url
106
+ "http://geolite.maxmind.com/download/geoip/database/"
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,38 @@
1
+ namespace :geocode do
2
+ desc "Geocode all objects without coordinates."
3
+ task :all => :environment do
4
+ class_name = ENV['CLASS'] || ENV['class']
5
+ sleep_timer = ENV['SLEEP'] || ENV['sleep']
6
+ batch = ENV['BATCH'] || ENV['batch']
7
+ reverse = ENV['REVERSE'] || ENV['reverse']
8
+ raise "Please specify a CLASS (model)" unless class_name
9
+ klass = class_from_string(class_name)
10
+ batch = batch.to_i unless batch.nil?
11
+ reverse = false unless reverse.to_s.downcase == 'true'
12
+
13
+ if reverse
14
+ klass.not_reverse_geocoded.find_each(batch_size: batch) do |obj|
15
+ obj.reverse_geocode; obj.save
16
+ sleep(sleep_timer.to_f) unless sleep_timer.nil?
17
+ end
18
+ else
19
+ klass.not_geocoded.find_each(batch_size: batch) do |obj|
20
+ obj.geocode; obj.save
21
+ sleep(sleep_timer.to_f) unless sleep_timer.nil?
22
+ end
23
+ end
24
+ end
25
+ end
26
+
27
+ ##
28
+ # Get a class object from the string given in the shell environment.
29
+ # Similar to ActiveSupport's +constantize+ method.
30
+ #
31
+ def class_from_string(class_name)
32
+ parts = class_name.split("::")
33
+ constant = Object
34
+ parts.each do |part|
35
+ constant = constant.const_get(part)
36
+ end
37
+ constant
38
+ end
@@ -0,0 +1,73 @@
1
+ require 'maxmind_database'
2
+
3
+ namespace :geocoder do
4
+ namespace :maxmind do
5
+ namespace :geolite do
6
+
7
+ desc "Download and load/refresh MaxMind GeoLite City data"
8
+ task load: [:download, :extract, :insert]
9
+
10
+ desc "Download MaxMind GeoLite City data"
11
+ task :download do
12
+ p = MaxmindTask.check_for_package!
13
+ MaxmindTask.download!(p, dir: ENV['DIR'] || "tmp/")
14
+ end
15
+
16
+ desc "Extract (unzip) MaxMind GeoLite City data"
17
+ task :extract do
18
+ p = MaxmindTask.check_for_package!
19
+ MaxmindTask.extract!(p, dir: ENV['DIR'] || "tmp/")
20
+ end
21
+
22
+ desc "Load/refresh MaxMind GeoLite City data"
23
+ task insert: [:environment] do
24
+ p = MaxmindTask.check_for_package!
25
+ MaxmindTask.insert!(p, dir: ENV['DIR'] || "tmp/")
26
+ end
27
+ end
28
+ end
29
+ end
30
+
31
+ module MaxmindTask
32
+ extend self
33
+
34
+ def check_for_package!
35
+ if %w[city country].include?(p = ENV['PACKAGE'])
36
+ return p
37
+ else
38
+ puts "Please specify PACKAGE=city or PACKAGE=country"
39
+ exit
40
+ end
41
+ end
42
+
43
+ def download!(package, options = {})
44
+ p = "geolite_#{package}_csv".intern
45
+ Geocoder::MaxmindDatabase.download(p, options[:dir])
46
+ end
47
+
48
+ def extract!(package, options = {})
49
+ begin
50
+ require 'zip'
51
+ rescue LoadError
52
+ puts "Please install gem: rubyzip (>= 1.0.0)"
53
+ exit
54
+ end
55
+ require 'fileutils'
56
+ p = "geolite_#{package}_csv".intern
57
+ archive_filename = Geocoder::MaxmindDatabase.archive_filename(p)
58
+ Zip::File.open(File.join(options[:dir], archive_filename)).each do |entry|
59
+ filepath = File.join(options[:dir], entry.name)
60
+ if File.exist? filepath
61
+ warn "File already exists (#{entry.name}), skipping"
62
+ else
63
+ FileUtils.mkdir_p(File.dirname(filepath))
64
+ entry.extract(filepath)
65
+ end
66
+ end
67
+ end
68
+
69
+ def insert!(package, options = {})
70
+ p = "geolite_#{package}_csv".intern
71
+ Geocoder::MaxmindDatabase.insert(p, options[:dir])
72
+ end
73
+ end