Implement IP2Country
							parent
							
								
									0a9f1f0e23
								
							
						
					
					
						commit
						51f0374109
					
				| @ -0,0 +1,116 @@ | |||||||
|  | package org.thoughtcrime.securesms.loki.utilities | ||||||
|  | 
 | ||||||
|  | import android.content.BroadcastReceiver | ||||||
|  | import android.content.Context | ||||||
|  | import android.content.Intent | ||||||
|  | import android.content.IntentFilter | ||||||
|  | import android.support.v4.content.LocalBroadcastManager | ||||||
|  | import android.util.Log | ||||||
|  | import com.opencsv.CSVReader | ||||||
|  | import org.whispersystems.signalservice.loki.api.onionrequests.OnionRequestAPI | ||||||
|  | import java.io.File | ||||||
|  | import java.io.FileOutputStream | ||||||
|  | import java.io.FileReader | ||||||
|  | 
 | ||||||
|  | class IP2Country private constructor(private val context: Context) { | ||||||
|  |     private val pathsBuiltEventReceiver: BroadcastReceiver | ||||||
|  |     private val countryNamesCache = mutableMapOf<String, String>() | ||||||
|  | 
 | ||||||
|  |     private val ipv4Table by lazy { | ||||||
|  |         loadFile("geolite2_country_blocks_ipv4.csv") | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private val countryNamesTable by lazy { | ||||||
|  |         loadFile("geolite2_country_locations_english.csv") | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // region Initialization | ||||||
|  |     companion object { | ||||||
|  | 
 | ||||||
|  |         public lateinit var shared: IP2Country | ||||||
|  | 
 | ||||||
|  |         public fun configureIfNeeded(context: Context) { | ||||||
|  |             if (::shared.isInitialized) { return; } | ||||||
|  |             shared = IP2Country(context) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     init { | ||||||
|  |         preloadCountriesIfNeeded() | ||||||
|  |         pathsBuiltEventReceiver = object : BroadcastReceiver() { | ||||||
|  | 
 | ||||||
|  |             override fun onReceive(context: Context, intent: Intent) { | ||||||
|  |                 preloadCountriesIfNeeded() | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         LocalBroadcastManager.getInstance(context).registerReceiver(pathsBuiltEventReceiver, IntentFilter("pathsBuilt")) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // TODO: Deinit? | ||||||
|  |     // endregion | ||||||
|  | 
 | ||||||
|  |     // region Implementation | ||||||
|  |     private fun loadFile(fileName: String): File { | ||||||
|  |         val directory = File(context.applicationInfo.dataDir) | ||||||
|  |         val file = File(directory, fileName) | ||||||
|  |         if (directory.list().contains(fileName)) { return file } | ||||||
|  |         val inputStream = context.assets.open("csv/$fileName") | ||||||
|  |         val outputStream = FileOutputStream(file) | ||||||
|  |         val buffer = ByteArray(1024) | ||||||
|  |         while (true) { | ||||||
|  |             val count = inputStream.read(buffer) | ||||||
|  |             if (count < 0) { break } | ||||||
|  |             outputStream.write(buffer, 0, count) | ||||||
|  |         } | ||||||
|  |         inputStream.close() | ||||||
|  |         outputStream.close() | ||||||
|  |         return file | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private fun getCountry(ip: String): String { | ||||||
|  |         var truncatedIP = ip | ||||||
|  |         fun getCountryInternal(): String { | ||||||
|  |             val country = countryNamesCache[ip] | ||||||
|  |             if (country != null) { return country } | ||||||
|  |             val ipv4TableReader = CSVReader(FileReader(ipv4Table.absoluteFile)) | ||||||
|  |             val countryNamesTableReader = CSVReader(FileReader(ipv4Table.absoluteFile)) | ||||||
|  |             var ipv4TableLine = ipv4TableReader.readNext() | ||||||
|  |             while (ipv4TableLine != null) { | ||||||
|  |                 if (!ipv4TableLine[0].startsWith(truncatedIP)) { | ||||||
|  |                     ipv4TableLine = ipv4TableReader.readNext() | ||||||
|  |                     continue | ||||||
|  |                 } | ||||||
|  |                 val countryID = ipv4TableLine[1] | ||||||
|  |                 var countryNamesTableLine = countryNamesTableReader.readNext() | ||||||
|  |                 while (countryNamesTableLine != null) { | ||||||
|  |                     if (countryNamesTableLine[0] != countryID) { | ||||||
|  |                         countryNamesTableLine = countryNamesTableReader.readNext() | ||||||
|  |                         continue | ||||||
|  |                     } | ||||||
|  |                     @Suppress("NAME_SHADOWING") val country = countryNamesTableLine[5] | ||||||
|  |                     countryNamesCache[ip] = country | ||||||
|  |                     return country | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             if (truncatedIP.contains(".") && !truncatedIP.endsWith(".")) { // The fuzziest we want to go is xxx.x | ||||||
|  |                 truncatedIP.dropLast(1) | ||||||
|  |                 if (truncatedIP.endsWith(".")) { truncatedIP.dropLast(1) } | ||||||
|  |                 return getCountryInternal() | ||||||
|  |             } else { | ||||||
|  |                 return "Unknown Country" | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         return getCountryInternal() | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private fun preloadCountriesIfNeeded() { | ||||||
|  |         Thread { | ||||||
|  |             val path = OnionRequestAPI.paths.firstOrNull() ?: return@Thread | ||||||
|  |             path.forEach { snode -> | ||||||
|  |                 getCountry(snode.ip) // Preload if needed | ||||||
|  |             } | ||||||
|  |             Log.d("Loki", "Finished preloading onion request path countries.") | ||||||
|  |         }.start() | ||||||
|  |     } | ||||||
|  |     // endregion | ||||||
|  | } | ||||||
					Loading…
					
					
				
		Reference in New Issue