-- | This module loads the MaxMind's GeoIp City database. module Network.GeoIp.GeoCityIp ( GeoCityDB, load, findRange, findLocation) where import qualified Data.ByteString as B import Data.Int import Data.Bits import Network.GeoIp.GeoDB data GeoCityDB = GeoCityDB GeoDB -- | Load the city database. If an error is triggered then -- Left is returned with an error string. load :: FilePath -> IO (Either String GeoCityDB) load geoFile = do eGeo <- makeGeoDB geoFile case eGeo of Left e -> return $ Left e Right geo -> if dataBaseType geo == geoIPCountryEdition then return $ Right (GeoCityDB geo) else return $ Left ("load: Incorrect database type. Database type is: " ++ (show $ dataBaseType geo)) extractRecordCity :: GeoDB -> Int -> (Double, Double) extractRecordCity geo cursor = (latitude, longitude) where recordCursor = cursor + (2 * (recordLength geo) - 1) * (dataBaseSegments geo) memo = B.drop recordCursor (mem geo) (_, countryMem) = getBytes 1 memo (_, regionMem) = extractNullString countryMem (_, cityMem) = extractNullString regionMem (_, postalMem) = extractNullString cityMem (latitude, latMem) = ((fromIntegral ((getNumber $ B.take 3 postalMem)::Integer)) / 10000 - 180, B.drop 3 postalMem) (longitude, _) = ((fromIntegral ((getNumber $ B.take 3 latMem)::Integer)) / 10000 - 180, B.drop 3 latMem) generateMask :: forall a. (Num a, Bits a) => Int -> Int -> a generateMask from to = if from <= to then (bit from) .|. generateMask (from+1) to else (bit from) -- | Find the IP range that the IP address is in. The result is monadic. -- In most cases you will want to use the Maybe monad. findRange :: (Monad m) => GeoCityDB -> Integer -> m (Integer, Integer) findRange (GeoCityDB geo) address = do (cursor, netMask) <- seekRecord geo address let bitMask = generateMask (31 - netMask) 31 hostMask = generateMask 0 netMask if cursor == dataBaseSegments geo then fail "Could not find IP" else return (address .&. bitMask, address .|. hostMask) -- | Find the location of an IP address. The tuple returned is @(latitude, longitude)@. -- The result is monadic, in most cases you will want to use the Maybe monad. findLocation :: (Monad m) => GeoCityDB -> Integer -> m (Double, Double) findLocation (GeoCityDB geo) address = do (cursor, _) <- seekRecord geo address if cursor == dataBaseSegments geo then fail "Could not find IP" else return (extractRecordCity geo cursor)