diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index 848546623..6f00b0bb8 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -634,7 +634,8 @@ C35E8AA32485C72300ACB629 /* SwiftCSV.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C35E8AA22485C72300ACB629 /* SwiftCSV.framework */; }; C35E8AA82485C85800ACB629 /* GeoLite2-Country-Locations-English.csv in Resources */ = {isa = PBXBuildFile; fileRef = C35E8AA52485C85400ACB629 /* GeoLite2-Country-Locations-English.csv */; }; C35E8AA92485C85800ACB629 /* GeoLite2-Country-Blocks-IPv4.csv in Resources */ = {isa = PBXBuildFile; fileRef = C35E8AA62485C85600ACB629 /* GeoLite2-Country-Blocks-IPv4.csv */; }; - C35E8AAC2485D1FE00ACB629 /* CSVUtilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = C35E8AAB2485D1FE00ACB629 /* CSVUtilities.swift */; }; + C35E8AAE2485E51D00ACB629 /* IP2Country.swift in Sources */ = {isa = PBXBuildFile; fileRef = C35E8AAD2485E51D00ACB629 /* IP2Country.swift */; }; + C35E8AB02485E86A00ACB629 /* GeneralUtilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = C35E8AAF2485E86A00ACB629 /* GeneralUtilities.swift */; }; C36B8707243C50C60049991D /* SignalMessaging.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 453518921FC63DBF00210559 /* SignalMessaging.framework */; }; C3DAB3242480CB2B00725F25 /* SRCopyableLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3DAB3232480CB2A00725F25 /* SRCopyableLabel.swift */; }; C3DFFAC623E96F0D0058DAF8 /* Sheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3DFFAC523E96F0D0058DAF8 /* Sheet.swift */; }; @@ -1517,7 +1518,8 @@ C35E8AA22485C72300ACB629 /* SwiftCSV.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftCSV.framework; path = ThirdParty/Carthage/Build/iOS/SwiftCSV.framework; sourceTree = ""; }; C35E8AA52485C85400ACB629 /* GeoLite2-Country-Locations-English.csv */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "GeoLite2-Country-Locations-English.csv"; sourceTree = ""; }; C35E8AA62485C85600ACB629 /* GeoLite2-Country-Blocks-IPv4.csv */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "GeoLite2-Country-Blocks-IPv4.csv"; sourceTree = ""; }; - C35E8AAB2485D1FE00ACB629 /* CSVUtilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CSVUtilities.swift; sourceTree = ""; }; + C35E8AAD2485E51D00ACB629 /* IP2Country.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IP2Country.swift; sourceTree = ""; }; + C35E8AAF2485E86A00ACB629 /* GeneralUtilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeneralUtilities.swift; sourceTree = ""; }; C3DAB3232480CB2A00725F25 /* SRCopyableLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SRCopyableLabel.swift; sourceTree = ""; }; C3DFFAC523E96F0D0058DAF8 /* Sheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Sheet.swift; sourceTree = ""; }; C3DFFAC723E970080058DAF8 /* OpenGroupSuggestionSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenGroupSuggestionSheet.swift; sourceTree = ""; }; @@ -2908,7 +2910,8 @@ children = ( B8544E3223D50E4900299F14 /* AppearanceUtilities.swift */, C31A6C5B247F2CF3001123EF /* CGRect+Utilities.swift */, - C35E8AAB2485D1FE00ACB629 /* CSVUtilities.swift */, + C35E8AAF2485E86A00ACB629 /* GeneralUtilities.swift */, + C35E8AAD2485E51D00ACB629 /* IP2Country.swift */, B84664F4235022F30083A1CD /* MentionUtilities.swift */, B886B4A82398BA1500211ABE /* QRCode.swift */, B8783E9D23EB948D00404FB8 /* UILabel+Interaction.swift */, @@ -4211,7 +4214,6 @@ 45FBC5C81DF8575700E9B410 /* CallKitCallManager.swift in Sources */, 4539B5861F79348F007141FF /* PushRegistrationManager.swift in Sources */, 45FBC5D11DF8592E00E9B410 /* SignalCall.swift in Sources */, - C35E8AAC2485D1FE00ACB629 /* CSVUtilities.swift in Sources */, B8B26C91234D8CBD004ED98C /* MentionCandidateSelectionViewDelegate.swift in Sources */, 340FC8BB204DAC8D007AEB0F /* OWSAddToContactViewController.m in Sources */, 45F32C232057297A00A300D5 /* MediaPageViewController.swift in Sources */, @@ -4249,6 +4251,7 @@ 34D1F0831F8678AA0066283D /* ConversationInputTextView.m in Sources */, 340FC8B6204DAC8D007AEB0F /* OWSQRCodeScanningViewController.m in Sources */, 4CB5F26920F7D060004D1B42 /* MessageActions.swift in Sources */, + C35E8AB02485E86A00ACB629 /* GeneralUtilities.swift in Sources */, 340FC8B5204DAC8D007AEB0F /* AboutTableViewController.m in Sources */, 34BECE2B1F74C12700D7438D /* DebugUIStress.m in Sources */, 340FC8B9204DAC8D007AEB0F /* UpdateGroupViewController.m in Sources */, @@ -4263,6 +4266,7 @@ B85357BF23A1AE0800AAF6CD /* SeedReminderView.swift in Sources */, B85357C723A1FB5100AAF6CD /* LinkDeviceVCDelegate.swift in Sources */, 340FC8C5204DE223007AEB0F /* DebugUIBackup.m in Sources */, + C35E8AAE2485E51D00ACB629 /* IP2Country.swift in Sources */, 4C11AA5020FD59C700351FBD /* MessageStatusView.swift in Sources */, 340FC8AE204DAC8D007AEB0F /* OWSSoundSettingsViewController.m in Sources */, 4579431E1E7C8CE9008ED0C0 /* Pastelog.m in Sources */, diff --git a/Signal/src/Loki/Utilities/CSVUtilities.swift b/Signal/src/Loki/Utilities/CSVUtilities.swift deleted file mode 100644 index 18acc217c..000000000 --- a/Signal/src/Loki/Utilities/CSVUtilities.swift +++ /dev/null @@ -1,4 +0,0 @@ -import SwiftCSV - -@inline(never) -public func preload(_ x: CSV) { } diff --git a/Signal/src/Loki/Utilities/GeneralUtilities.swift b/Signal/src/Loki/Utilities/GeneralUtilities.swift new file mode 100644 index 000000000..777925584 --- /dev/null +++ b/Signal/src/Loki/Utilities/GeneralUtilities.swift @@ -0,0 +1,3 @@ + +@inline(never) +public func touch(_ x: Any?) { } diff --git a/Signal/src/Loki/Utilities/IP2Country.swift b/Signal/src/Loki/Utilities/IP2Country.swift new file mode 100644 index 000000000..853e2cdc1 --- /dev/null +++ b/Signal/src/Loki/Utilities/IP2Country.swift @@ -0,0 +1,61 @@ +import SwiftCSV + +final class IP2Country { + + private static let ipv4Table = try! CSV(name: "GeoLite2-Country-Blocks-IPv4", extension: "csv", bundle: .main, delimiter: ",", encoding: .utf8, loadColumns: true)! + private static let countryNamesTable = try! CSV(name: "GeoLite2-Country-Locations-English", extension: "csv", bundle: .main, delimiter: ",", encoding: .utf8, loadColumns: true)! + private static var countryNamesCache: [String:String] = [:] + + // MARK: Lifecycle + static let shared = IP2Country() + + private init() { + preloadCountriesIfNeeded() + NotificationCenter.default.addObserver(self, selector: #selector(preloadCountriesIfNeeded), name: .pathsBuilt, object: nil) + } + + deinit { + NotificationCenter.default.removeObserver(self) + } + + // MARK: Implementation + static func getCountry(_ ip: String) -> String { + var truncatedIP = ip + func getCountryInternal() -> String { + if let country = countryNamesCache[ip] { return country } + if let ipv4TableIndex = ipv4Table.namedColumns["network"]!.firstIndex(where: { $0.starts(with: truncatedIP) }) { + let countryID = ipv4Table.namedColumns["registered_country_geoname_id"]![ipv4TableIndex] + if let countryNamesTableIndex = countryNamesTable.namedColumns["geoname_id"]!.firstIndex(of: countryID) { + let country = countryNamesTable.namedColumns["country_name"]![countryNamesTableIndex] + countryNamesCache[ip] = country + return country + } + } + if truncatedIP.contains(".") && !truncatedIP.hasSuffix(".") { // The fuzziest we want to go is xxx.x + truncatedIP.removeLast() + if truncatedIP.hasSuffix(".") { truncatedIP.removeLast() } + return getCountryInternal() + } else { + return "Unknown Country" + } + } + return getCountryInternal() + } + + @objc private func preloadCountriesIfNeeded() { + DispatchQueue.global(qos: .userInitiated).async { + if OnionRequestAPI.paths.count < OnionRequestAPI.pathCount { + let storage = OWSPrimaryStorage.shared() + storage.dbReadConnection.read { transaction in + OnionRequestAPI.paths = storage.getOnionRequestPaths(in: transaction) + } + } + guard OnionRequestAPI.paths.count >= OnionRequestAPI.pathCount else { return } + let pathToDisplay = OnionRequestAPI.paths.first! + pathToDisplay.forEach { snode in + let _ = IP2Country.getCountry(snode.ip) // Preload if needed + } + print("[Loki] Finished preloading onion request path countries.") + } + } +} diff --git a/Signal/src/Loki/View Controllers/HomeVC.swift b/Signal/src/Loki/View Controllers/HomeVC.swift index 5688402a8..578b328f0 100644 --- a/Signal/src/Loki/View Controllers/HomeVC.swift +++ b/Signal/src/Loki/View Controllers/HomeVC.swift @@ -84,10 +84,7 @@ final class HomeVC : BaseVC, UITableViewDataSource, UITableViewDelegate, UIScrol // MARK: Lifecycle override func viewDidLoad() { super.viewDidLoad() - DispatchQueue.global(qos: .userInitiated).async { - preload(PathVC.ipv4Table) - preload(PathVC.countryNamesTable) - } + touch(IP2Country.shared) SignalApp.shared().homeViewController = self setUpGradientBackground() if navigationController?.navigationBar != nil { diff --git a/Signal/src/Loki/View Controllers/PathVC.swift b/Signal/src/Loki/View Controllers/PathVC.swift index 732c8c151..c48035ab1 100644 --- a/Signal/src/Loki/View Controllers/PathVC.swift +++ b/Signal/src/Loki/View Controllers/PathVC.swift @@ -3,10 +3,6 @@ import SwiftCSV final class PathVC : BaseVC { - static let ipv4Table = try! CSV(name: "GeoLite2-Country-Blocks-IPv4", extension: "csv", bundle: .main, delimiter: ",", encoding: .utf8, loadColumns: true)! - static let countryNamesTable = try! CSV(name: "GeoLite2-Country-Locations-English", extension: "csv", bundle: .main, delimiter: ",", encoding: .utf8, loadColumns: true)! - static var countryNamesCache: [String:String] = [:] - // MARK: Components private lazy var pathStackView: UIStackView = { let result = UIStackView() @@ -159,27 +155,9 @@ final class PathVC : BaseVC { } private func getPathRow(snode: LokiAPITarget, location: LineView.Location, dotAnimationStartDelay: Double, dotAnimationRepeatInterval: Double, isGuardSnode: Bool) -> UIStackView { - var snodeIP = snode.ip - func getCountry() -> String { - if let country = PathVC.countryNamesCache[snode.address] { return country } - if let ipv4TableIndex = PathVC.ipv4Table.namedColumns["network"]!.firstIndex(where: { $0.starts(with: snodeIP) }) { - let countryID = PathVC.ipv4Table.namedColumns["registered_country_geoname_id"]![ipv4TableIndex] - if let countryNamesTableIndex = PathVC.countryNamesTable.namedColumns["geoname_id"]!.firstIndex(of: countryID) { - let country = PathVC.countryNamesTable.namedColumns["country_name"]![countryNamesTableIndex] - PathVC.countryNamesCache[snode.address] = country - return country - } - } - if snodeIP.contains(".") && !snodeIP.hasSuffix(".") { // The fuzziest we want to go is xxx.x - snodeIP.removeLast() - if snodeIP.hasSuffix(".") { snodeIP.removeLast() } - return getCountry() - } else { - return "Unknown Country" - } - } + let country = IP2Country.getCountry(snode.ip) let title = isGuardSnode ? NSLocalizedString("Guard Node", comment: "") : NSLocalizedString("Service Node", comment: "") - return getPathRow(title: title, subtitle: getCountry(), location: location, dotAnimationStartDelay: dotAnimationStartDelay, dotAnimationRepeatInterval: dotAnimationRepeatInterval) + return getPathRow(title: title, subtitle: country, location: location, dotAnimationStartDelay: dotAnimationStartDelay, dotAnimationRepeatInterval: dotAnimationRepeatInterval) } // MARK: Interaction