]> git.ipfire.org Git - thirdparty/wireguard-apple.git/commitdiff
iOS: Ability to view the log
authorRoopesh Chander <roop@roopc.net>
Thu, 28 Mar 2019 13:58:27 +0000 (19:28 +0530)
committerRoopesh Chander <roop@roopc.net>
Thu, 28 Mar 2019 13:58:27 +0000 (19:28 +0530)
Signed-off-by: Roopesh Chander <roop@roopc.net>
WireGuard/WireGuard.xcodeproj/project.pbxproj
WireGuard/WireGuard/Base.lproj/Localizable.strings
WireGuard/WireGuard/UI/iOS/ViewController/LogViewController.swift [new file with mode: 0644]
WireGuard/WireGuard/UI/iOS/ViewController/SettingsTableViewController.swift

index 53bc8c1f7b18e8104ecb53c60c3c9a9af9b2967f..9ae6f0ff59436592d097b5ed6d76afcff3b71cf5 100644 (file)
                6FDB3C3C21DCF6BB00A0C0BF /* TunnelViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F628C3C217F09E9003482A3 /* TunnelViewModel.swift */; };
                6FDB6D13224A15BF00EE4BC3 /* LogViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FDB6D12224A15BE00EE4BC3 /* LogViewController.swift */; };
                6FDB6D15224CB2CE00EE4BC3 /* LogViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FDB6D14224CB2CE00EE4BC3 /* LogViewCell.swift */; };
+               6FDB6D18224CC05A00EE4BC3 /* LogViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FDB6D16224CC04E00EE4BC3 /* LogViewController.swift */; };
                6FDEF7E421846C1A00D8FBF6 /* libwg-go.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 6FDEF7E321846C1A00D8FBF6 /* libwg-go.a */; };
                6FDEF7E62185EFB200D8FBF6 /* QRScanViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FDEF7E52185EFAF00D8FBF6 /* QRScanViewController.swift */; };
                6FDEF7FB21863B6100D8FBF6 /* unzip.c in Sources */ = {isa = PBXBuildFile; fileRef = 6FDEF7F621863B6100D8FBF6 /* unzip.c */; };
                6FDB3C3A21DCF47400A0C0BF /* TunnelDetailTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelDetailTableViewController.swift; sourceTree = "<group>"; };
                6FDB6D12224A15BE00EE4BC3 /* LogViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LogViewController.swift; sourceTree = "<group>"; };
                6FDB6D14224CB2CE00EE4BC3 /* LogViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogViewCell.swift; sourceTree = "<group>"; };
+               6FDB6D16224CC04E00EE4BC3 /* LogViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogViewController.swift; sourceTree = "<group>"; };
                6FDEF7E321846C1A00D8FBF6 /* libwg-go.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = "libwg-go.a"; sourceTree = BUILT_PRODUCTS_DIR; };
                6FDEF7E52185EFAF00D8FBF6 /* QRScanViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QRScanViewController.swift; sourceTree = "<group>"; };
                6FDEF7F621863B6100D8FBF6 /* unzip.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = unzip.c; sourceTree = "<group>"; };
                                6F7774DF217181B1006A79B3 /* MainViewController.swift */,
                                6F8F0D7622267C57000E8335 /* SSIDOptionEditTableViewController.swift */,
                                6F9B8A8D223398610041B9C4 /* SSIDOptionDetailTableViewController.swift */,
+                               6FDB6D16224CC04E00EE4BC3 /* LogViewController.swift */,
                        );
                        path = ViewController;
                        sourceTree = "<group>";
                                6B586C53220CBA6D00427C51 /* Data+KeyEncoding.swift in Sources */,
                                6F693A562179E556008551C1 /* Endpoint.swift in Sources */,
                                6FDEF7E62185EFB200D8FBF6 /* QRScanViewController.swift in Sources */,
+                               6FDB6D18224CC05A00EE4BC3 /* LogViewController.swift in Sources */,
                                6FFA5D952194454A0001E2F7 /* NETunnelProviderProtocol+Extension.swift in Sources */,
                                5FF7B96221CC95DE00A7DD74 /* InterfaceConfiguration.swift in Sources */,
                                5F4541A921C451D100994C13 /* TunnelStatus.swift in Sources */,
index 08725969ad5c833c22a034766ead4af0c998b440..c4e9cb158ed6cbb090dcf37216927897927d7a8b 100644 (file)
 "settingsSectionTitleExportConfigurations" = "Export configurations";
 "settingsExportZipButtonTitle" = "Export zip archive";
 
-"settingsSectionTitleTunnelLog" = "Tunnel log";
-"settingsExportLogFileButtonTitle" = "Export log file";
+"settingsSectionTitleTunnelLog" = "Log";
+"settingsViewLogButtonTitle" = "View log";
 
-// Settings alerts
+// Log view
+
+"logViewTitle" = "Log";
+
+// Log alerts
 
 "alertUnableToRemovePreviousLogTitle" = "Log export failed";
 "alertUnableToRemovePreviousLogMessage" = "The pre-existing log could not be cleared";
diff --git a/WireGuard/WireGuard/UI/iOS/ViewController/LogViewController.swift b/WireGuard/WireGuard/UI/iOS/ViewController/LogViewController.swift
new file mode 100644 (file)
index 0000000..bcfbaf5
--- /dev/null
@@ -0,0 +1,127 @@
+// SPDX-License-Identifier: MIT
+// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
+
+import UIKit
+
+class LogViewController: UIViewController {
+
+    let textView: UITextView = {
+        let textView = UITextView()
+        textView.isEditable = false
+        textView.isSelectable = false
+        textView.font = UIFont.preferredFont(forTextStyle: UIFont.TextStyle.body)
+        textView.adjustsFontForContentSizeCategory = true
+        return textView
+    }()
+
+    let busyIndicator: UIActivityIndicatorView = {
+        let busyIndicator = UIActivityIndicatorView(style: .gray)
+        busyIndicator.hidesWhenStopped = true
+        return busyIndicator
+    }()
+
+    var logViewHelper: LogViewHelper?
+    var isFetchingLogEntries = false
+    private var updateLogEntriesTimer: Timer?
+
+    override func loadView() {
+        view = UIView()
+        view.backgroundColor = .white
+
+        view.addSubview(textView)
+        textView.translatesAutoresizingMaskIntoConstraints = false
+        NSLayoutConstraint.activate([
+            textView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
+            textView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
+            textView.topAnchor.constraint(equalTo: view.topAnchor),
+            textView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
+        ])
+
+        view.addSubview(busyIndicator)
+        busyIndicator.translatesAutoresizingMaskIntoConstraints = false
+        NSLayoutConstraint.activate([
+            busyIndicator.centerXAnchor.constraint(equalTo: view.centerXAnchor),
+            busyIndicator.centerYAnchor.constraint(equalTo: view.centerYAnchor)
+        ])
+
+        busyIndicator.startAnimating()
+
+        logViewHelper = LogViewHelper(logFilePath: FileManager.logFileURL?.path)
+        startUpdatingLogEntries()
+    }
+
+    override func viewDidLoad() {
+        title = tr("logViewTitle")
+        navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .save, target: self, action: #selector(saveTapped(sender:)))
+    }
+
+    func updateLogEntries() {
+        guard !isFetchingLogEntries else { return }
+        isFetchingLogEntries = true
+        logViewHelper?.fetchLogEntriesSinceLastFetch { [weak self] fetchedLogEntries in
+            guard let self = self else { return }
+            defer {
+                self.isFetchingLogEntries = false
+            }
+            if self.busyIndicator.isAnimating {
+                self.busyIndicator.stopAnimating()
+            }
+            guard !fetchedLogEntries.isEmpty else { return }
+            let isScrolledToEnd = self.textView.contentSize.height - self.textView.bounds.height - self.textView.contentOffset.y < 1
+            let text = fetchedLogEntries.reduce("") { $0 + $1.text() + "\n" }
+            self.textView.insertText(text)
+            if isScrolledToEnd {
+                let endOfCurrentText = NSRange(location: (self.textView.text as NSString).length, length: 0)
+                self.textView.scrollRangeToVisible(endOfCurrentText)
+            }
+        }
+    }
+
+    func startUpdatingLogEntries() {
+        updateLogEntries()
+        updateLogEntriesTimer?.invalidate()
+        let timer = Timer(timeInterval: 1 /* second */, repeats: true) { [weak self] _ in
+            self?.updateLogEntries()
+        }
+        updateLogEntriesTimer = timer
+        RunLoop.main.add(timer, forMode: .common)
+    }
+
+    @objc func saveTapped(sender: AnyObject) {
+        guard let destinationDir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else { return }
+
+        let dateFormatter = ISO8601DateFormatter()
+        dateFormatter.formatOptions = [.withFullDate, .withTime, .withTimeZone] // Avoid ':' in the filename
+        let timeStampString = dateFormatter.string(from: Date())
+        let destinationURL = destinationDir.appendingPathComponent("wireguard-log-\(timeStampString).txt")
+
+        DispatchQueue.global(qos: .userInitiated).async {
+
+            if FileManager.default.fileExists(atPath: destinationURL.path) {
+                let isDeleted = FileManager.deleteFile(at: destinationURL)
+                if !isDeleted {
+                    ErrorPresenter.showErrorAlert(title: tr("alertUnableToRemovePreviousLogTitle"), message: tr("alertUnableToRemovePreviousLogMessage"), from: self)
+                    return
+                }
+            }
+
+            let isWritten = Logger.global?.writeLog(to: destinationURL.path) ?? false
+
+            DispatchQueue.main.async {
+                guard isWritten else {
+                    ErrorPresenter.showErrorAlert(title: tr("alertUnableToWriteLogTitle"), message: tr("alertUnableToWriteLogMessage"), from: self)
+                    return
+                }
+                let activityVC = UIActivityViewController(activityItems: [destinationURL], applicationActivities: nil)
+                if let sender = sender as? UIBarButtonItem {
+                    activityVC.popoverPresentationController?.barButtonItem = sender
+                }
+                activityVC.completionWithItemsHandler = { _, _, _, _ in
+                    // Remove the exported log file after the activity has completed
+                    _ = FileManager.deleteFile(at: destinationURL)
+                }
+                self.present(activityVC, animated: true)
+            }
+        }
+    }
+}
index ff83b2c0b6a00bbca315955f87823ed60595b114..9956b7b890ba426de88e3cc8deb7d865517981c1 100644 (file)
@@ -10,14 +10,14 @@ class SettingsTableViewController: UITableViewController {
         case iosAppVersion
         case goBackendVersion
         case exportZipArchive
-        case exportLogFile
+        case viewLog
 
         var localizedUIString: String {
             switch self {
             case .iosAppVersion: return tr("settingsVersionKeyWireGuardForIOS")
             case .goBackendVersion: return tr("settingsVersionKeyWireGuardGoBackend")
             case .exportZipArchive: return tr("settingsExportZipButtonTitle")
-            case .exportLogFile: return tr("settingsExportLogFileButtonTitle")
+            case .viewLog: return tr("settingsViewLogButtonTitle")
             }
         }
     }
@@ -25,7 +25,7 @@ class SettingsTableViewController: UITableViewController {
     let settingsFieldsBySection: [[SettingsFields]] = [
         [.iosAppVersion, .goBackendVersion],
         [.exportZipArchive],
-        [.exportLogFile]
+        [.viewLog]
     ]
 
     let tunnelsManager: TunnelsManager?
@@ -108,41 +108,10 @@ class SettingsTableViewController: UITableViewController {
         }
     }
 
-    func exportLogForLastActivatedTunnel(sourceView: UIView) {
-        guard let destinationDir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else { return }
+    func presentLogView() {
+        let logVC = LogViewController()
+        navigationController?.pushViewController(logVC, animated: true)
 
-        let dateFormatter = ISO8601DateFormatter()
-        dateFormatter.formatOptions = [.withFullDate, .withTime, .withTimeZone] // Avoid ':' in the filename
-        let timeStampString = dateFormatter.string(from: Date())
-        let destinationURL = destinationDir.appendingPathComponent("wireguard-log-\(timeStampString).txt")
-
-        DispatchQueue.global(qos: .userInitiated).async {
-
-            if FileManager.default.fileExists(atPath: destinationURL.path) {
-                let isDeleted = FileManager.deleteFile(at: destinationURL)
-                if !isDeleted {
-                    ErrorPresenter.showErrorAlert(title: tr("alertUnableToRemovePreviousLogTitle"), message: tr("alertUnableToRemovePreviousLogMessage"), from: self)
-                    return
-                }
-            }
-
-            let isWritten = Logger.global?.writeLog(to: destinationURL.path) ?? false
-
-            DispatchQueue.main.async {
-                guard isWritten else {
-                    ErrorPresenter.showErrorAlert(title: tr("alertUnableToWriteLogTitle"), message: tr("alertUnableToWriteLogMessage"), from: self)
-                    return
-                }
-                let activityVC = UIActivityViewController(activityItems: [destinationURL], applicationActivities: nil)
-                activityVC.popoverPresentationController?.sourceView = sourceView
-                activityVC.popoverPresentationController?.sourceRect = sourceView.bounds
-                activityVC.completionWithItemsHandler = { _, _, _, _ in
-                    // Remove the exported log file after the activity has completed
-                    _ = FileManager.deleteFile(at: destinationURL)
-                }
-                self.present(activityVC, animated: true)
-            }
-        }
     }
 }
 
@@ -192,11 +161,11 @@ extension SettingsTableViewController {
             }
             return cell
         } else {
-            assert(field == .exportLogFile)
+            assert(field == .viewLog)
             let cell: ButtonCell = tableView.dequeueReusableCell(for: indexPath)
             cell.buttonText = field.localizedUIString
             cell.onTapped = { [weak self] in
-                self?.exportLogForLastActivatedTunnel(sourceView: cell.button)
+                self?.presentLogView()
             }
             return cell
         }