2018-10-29 12:04:02 +00:00
// S P D X - L i c e n s e - I d e n t i f i e r : M I T
2018-10-30 02:57:35 +00:00
// C o p y r i g h t © 2 0 1 8 W i r e G u a r d L L C . A l l R i g h t s R e s e r v e d .
2018-10-29 12:04:02 +00:00
import UIKit
2018-10-29 17:26:25 +00:00
import os . log
2018-10-29 12:04:02 +00:00
class SettingsTableViewController : UITableViewController {
enum SettingsFields : String {
case iosAppVersion = " WireGuard for iOS "
case goBackendVersion = " WireGuard Go Backend "
case exportZipArchive = " Export zip archive "
2018-11-29 19:25:42 +00:00
case exportLogFile = " Export log file "
2018-10-29 12:04:02 +00:00
}
2018-11-06 17:11:54 +00:00
let settingsFieldsBySection : [ [ SettingsFields ] ] = [
2018-11-03 18:53:04 +00:00
[ . iosAppVersion , . goBackendVersion ] ,
2018-11-29 19:25:42 +00:00
[ . exportZipArchive ] ,
[ . exportLogFile ]
2018-10-29 12:04:02 +00:00
]
2018-10-29 17:26:25 +00:00
let tunnelsManager : TunnelsManager ?
2018-11-03 18:35:25 +00:00
var wireguardCaptionedImage : ( view : UIView , size : CGSize ) ?
2018-10-29 17:26:25 +00:00
init ( tunnelsManager : TunnelsManager ? ) {
self . tunnelsManager = tunnelsManager
2018-10-29 12:04:02 +00:00
super . init ( style : . grouped )
}
required init ? ( coder aDecoder : NSCoder ) {
fatalError ( " init(coder:) has not been implemented " )
}
override func viewDidLoad ( ) {
super . viewDidLoad ( )
self . title = " Settings "
self . navigationItem . rightBarButtonItem = UIBarButtonItem ( barButtonSystemItem : . done , target : self , action : #selector ( doneTapped ) )
self . tableView . rowHeight = 44
self . tableView . allowsSelection = false
self . tableView . register ( TunnelSettingsTableViewKeyValueCell . self , forCellReuseIdentifier : TunnelSettingsTableViewKeyValueCell . id )
self . tableView . register ( TunnelSettingsTableViewButtonCell . self , forCellReuseIdentifier : TunnelSettingsTableViewButtonCell . id )
2018-11-03 13:11:54 +00:00
2018-11-03 18:48:10 +00:00
let logo = UIImageView ( image : UIImage ( named : " wireguard.pdf " , in : Bundle . main , compatibleWith : nil ) ! )
logo . contentMode = . scaleAspectFit
2018-11-04 02:37:09 +00:00
let height = self . tableView . rowHeight * 1.5
let width = height * logo . image ! . size . width / logo . image ! . size . height
logo . frame = CGRect ( x : 0 , y : 0 , width : width , height : height )
logo . bounds = logo . frame . insetBy ( dx : 2 , dy : 2 )
2018-11-03 18:48:10 +00:00
self . tableView . tableFooterView = logo
2018-10-29 12:04:02 +00:00
}
2018-11-05 01:09:40 +00:00
2018-11-04 02:37:09 +00:00
override func viewDidLayoutSubviews ( ) {
super . viewDidLayoutSubviews ( )
guard let logo = self . tableView . tableFooterView else { return }
2018-11-05 01:09:40 +00:00
let bottomPadding = max ( self . tableView . layoutMargins . bottom , CGFloat ( 10 ) )
let fullHeight = max ( self . tableView . contentSize . height , self . tableView . bounds . size . height - self . tableView . layoutMargins . top - bottomPadding )
2018-11-04 02:37:09 +00:00
let e = logo . frame
2018-11-05 01:09:40 +00:00
logo . frame = CGRect ( x : e . minX , y : fullHeight - e . height , width : e . width , height : e . height )
2018-11-04 02:37:09 +00:00
}
2018-10-29 12:04:02 +00:00
@objc func doneTapped ( ) {
dismiss ( animated : true , completion : nil )
}
2018-10-29 17:26:25 +00:00
func exportConfigurationsAsZipFile ( sourceView : UIView ) {
2018-11-15 07:59:49 +00:00
guard let tunnelsManager = tunnelsManager else { return }
2018-10-29 17:26:25 +00:00
guard let destinationDir = FileManager . default . urls ( for : . documentDirectory , in : . userDomainMask ) . first else {
return
}
let destinationURL = destinationDir . appendingPathComponent ( " wireguard-export.zip " )
do {
try FileManager . default . removeItem ( at : destinationURL )
} catch {
os_log ( " Failed to delete file: %{public}@ : %{public}@ " , log : OSLog . default , type : . error , destinationURL . absoluteString , error . localizedDescription )
}
2018-11-15 07:59:49 +00:00
let count = tunnelsManager . numberOfTunnels ( )
let tunnelConfigurations = ( 0 . . < count ) . compactMap { tunnelsManager . tunnel ( at : $0 ) . tunnelConfiguration ( ) }
2018-11-15 08:07:59 +00:00
ZipExporter . exportConfigFiles ( tunnelConfigurations : tunnelConfigurations , to : destinationURL ) { [ weak self ] ( error ) in
if let error = error {
ErrorPresenter . showErrorAlert ( error : error , from : self )
return
}
let activityVC = UIActivityViewController ( activityItems : [ destinationURL ] , applicationActivities : nil )
// p o p o v e r P r e s e n t a t i o n C o n t r o l l e r s h a l l b e n o n - n i l o n t h e i P a d
activityVC . popoverPresentationController ? . sourceView = sourceView
activityVC . popoverPresentationController ? . sourceRect = sourceView . bounds
self ? . present ( activityVC , animated : true )
2018-10-29 17:26:25 +00:00
}
}
2018-11-29 19:25:42 +00:00
func exportLogForLastActivatedTunnel ( sourceView : UIView ) {
guard let destinationDir = FileManager . default . urls ( for : . documentDirectory , in : . userDomainMask ) . first else {
return
}
let destinationURL = destinationDir . appendingPathComponent ( " WireGuard_iOS_log.txt " )
if ( FileManager . default . fileExists ( atPath : destinationURL . path ) ) {
do {
try FileManager . default . removeItem ( at : destinationURL )
} catch {
os_log ( " Failed to delete file: %{public}@ : %{public}@ " , log : OSLog . default , type : . error , destinationURL . absoluteString , error . localizedDescription )
showErrorAlert ( title : " No log available " , message : " The pre-existing log could not be cleared " )
return
}
}
guard let networkExtensionLogFileURL = FileManager . networkExtensionLogFileURL ,
FileManager . default . fileExists ( atPath : networkExtensionLogFileURL . path ) else {
showErrorAlert ( title : " No log available " , message : " Please activate a tunnel and then export the log " )
return
}
do {
try FileManager . default . copyItem ( at : networkExtensionLogFileURL , to : destinationURL )
} catch {
os_log ( " Failed to copy file: %{public}@ to %{public}@: %{public}@ " , log : OSLog . default , type : . error , networkExtensionLogFileURL . absoluteString , destinationURL . absoluteString , error . localizedDescription )
showErrorAlert ( title : " No log available " , message : " The log could not be accessed " )
return
}
let activityVC = UIActivityViewController ( activityItems : [ destinationURL ] , applicationActivities : nil )
// p o p o v e r P r e s e n t a t i o n C o n t r o l l e r s h a l l b e n o n - n i l o n t h e i P a d
activityVC . popoverPresentationController ? . sourceView = sourceView
activityVC . popoverPresentationController ? . sourceRect = sourceView . bounds
self . present ( activityVC , animated : true )
}
2018-10-29 17:26:25 +00:00
func showErrorAlert ( title : String , message : String ) {
2018-11-01 20:22:12 +00:00
let okAction = UIAlertAction ( title : " OK " , style : . default )
2018-10-29 17:26:25 +00:00
let alert = UIAlertController ( title : title , message : message , preferredStyle : . alert )
alert . addAction ( okAction )
self . present ( alert , animated : true , completion : nil )
}
2018-10-29 12:04:02 +00:00
}
// MARK: U I T a b l e V i e w D a t a S o u r c e
extension SettingsTableViewController {
override func numberOfSections ( in tableView : UITableView ) -> Int {
return settingsFieldsBySection . count
}
override func tableView ( _ tableView : UITableView , numberOfRowsInSection section : Int ) -> Int {
return settingsFieldsBySection [ section ] . count
}
override func tableView ( _ tableView : UITableView , titleForHeaderInSection section : Int ) -> String ? {
switch ( section ) {
case 0 :
2018-11-03 12:23:50 +00:00
return " About "
2018-11-03 18:53:04 +00:00
case 1 :
return " Export configurations "
2018-11-29 19:25:42 +00:00
case 2 :
return " Tunnel log "
2018-10-29 12:04:02 +00:00
default :
return nil
}
}
override func tableView ( _ tableView : UITableView , cellForRowAt indexPath : IndexPath ) -> UITableViewCell {
let field = settingsFieldsBySection [ indexPath . section ] [ indexPath . row ]
if ( field = = . iosAppVersion || field = = . goBackendVersion ) {
let cell = tableView . dequeueReusableCell ( withIdentifier : TunnelSettingsTableViewKeyValueCell . id , for : indexPath ) as ! TunnelSettingsTableViewKeyValueCell
cell . key = field . rawValue
if ( field = = . iosAppVersion ) {
2018-11-07 04:45:39 +00:00
var appVersion = Bundle . main . infoDictionary ? [ " CFBundleShortVersionString " ] as ? String ? ? " Unknown version "
if let appBuild = Bundle . main . infoDictionary ? [ " CFBundleVersion " ] as ? String {
appVersion += " ( \( appBuild ) ) "
}
2018-10-29 12:04:02 +00:00
cell . value = appVersion
} else if ( field = = . goBackendVersion ) {
2018-10-31 03:02:36 +00:00
cell . value = WIREGUARD_GO_VERSION
2018-10-29 12:04:02 +00:00
}
return cell
2018-11-29 19:25:42 +00:00
} else if ( field = = . exportZipArchive ) {
2018-10-29 12:04:02 +00:00
let cell = tableView . dequeueReusableCell ( withIdentifier : TunnelSettingsTableViewButtonCell . id , for : indexPath ) as ! TunnelSettingsTableViewButtonCell
cell . buttonText = field . rawValue
2018-10-29 17:26:25 +00:00
cell . onTapped = { [ weak self ] in
self ? . exportConfigurationsAsZipFile ( sourceView : cell . button )
}
2018-10-29 12:04:02 +00:00
return cell
2018-11-29 19:25:42 +00:00
} else {
assert ( field = = . exportLogFile )
let cell = tableView . dequeueReusableCell ( withIdentifier : TunnelSettingsTableViewButtonCell . id , for : indexPath ) as ! TunnelSettingsTableViewButtonCell
cell . buttonText = field . rawValue
cell . onTapped = { [ weak self ] in
self ? . exportLogForLastActivatedTunnel ( sourceView : cell . button )
}
return cell
2018-10-29 12:04:02 +00:00
}
}
}
class TunnelSettingsTableViewKeyValueCell : UITableViewCell {
static let id : String = " TunnelSettingsTableViewKeyValueCell "
var key : String {
get { return textLabel ? . text ? ? " " }
set ( value ) { textLabel ? . text = value }
}
var value : String {
get { return detailTextLabel ? . text ? ? " " }
set ( value ) { detailTextLabel ? . text = value }
}
2018-11-05 05:31:25 +00:00
override init ( style : UITableViewCell . CellStyle , reuseIdentifier : String ? ) {
2018-10-29 12:04:02 +00:00
super . init ( style : . value1 , reuseIdentifier : TunnelSettingsTableViewKeyValueCell . id )
}
required init ? ( coder aDecoder : NSCoder ) {
fatalError ( " init(coder:) has not been implemented " )
}
override func prepareForReuse ( ) {
super . prepareForReuse ( )
key = " "
value = " "
}
}
class TunnelSettingsTableViewButtonCell : UITableViewCell {
static let id : String = " TunnelSettingsTableViewButtonCell "
var buttonText : String {
get { return button . title ( for : . normal ) ? ? " " }
set ( value ) { button . setTitle ( value , for : . normal ) }
}
2018-11-03 18:35:25 +00:00
var onTapped : ( ( ) -> Void ) ?
2018-10-29 12:04:02 +00:00
let button : UIButton
2018-11-05 05:31:25 +00:00
override init ( style : UITableViewCell . CellStyle , reuseIdentifier : String ? ) {
2018-10-29 12:04:02 +00:00
button = UIButton ( type : . system )
super . init ( style : style , reuseIdentifier : reuseIdentifier )
contentView . addSubview ( button )
button . translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint . activate ( [
button . centerYAnchor . constraint ( equalTo : contentView . centerYAnchor ) ,
button . centerXAnchor . constraint ( equalTo : contentView . centerXAnchor )
] )
button . addTarget ( self , action : #selector ( buttonTapped ) , for : . touchUpInside )
}
@objc func buttonTapped ( ) {
onTapped ? ( )
}
required init ? ( coder aDecoder : NSCoder ) {
fatalError ( " init(coder:) has not been implemented " )
}
override func prepareForReuse ( ) {
super . prepareForReuse ( )
buttonText = " "
onTapped = nil
}
}