diff options
author | Roopesh Chander <roop@roopc.net> | 2019-03-28 19:28:27 +0530 |
---|---|---|
committer | Roopesh Chander <roop@roopc.net> | 2019-03-28 19:28:27 +0530 |
commit | 6175de0438b2e501815aec7cd625babf1f48387d (patch) | |
tree | 103ca327b4c88b9c565e7d06d0de81816e6dbd77 /WireGuard/WireGuard | |
parent | iOS: Xcode: Minor project rearrangement (diff) | |
download | wireguard-apple-6175de0438b2e501815aec7cd625babf1f48387d.tar.xz wireguard-apple-6175de0438b2e501815aec7cd625babf1f48387d.zip |
iOS: Ability to view the log
Signed-off-by: Roopesh Chander <roop@roopc.net>
Diffstat (limited to 'WireGuard/WireGuard')
3 files changed, 142 insertions, 42 deletions
diff --git a/WireGuard/WireGuard/Base.lproj/Localizable.strings b/WireGuard/WireGuard/Base.lproj/Localizable.strings index 0872596..c4e9cb1 100644 --- a/WireGuard/WireGuard/Base.lproj/Localizable.strings +++ b/WireGuard/WireGuard/Base.lproj/Localizable.strings @@ -209,10 +209,14 @@ "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 index 0000000..bcfbaf5 --- /dev/null +++ b/WireGuard/WireGuard/UI/iOS/ViewController/LogViewController.swift @@ -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) + } + } + } +} diff --git a/WireGuard/WireGuard/UI/iOS/ViewController/SettingsTableViewController.swift b/WireGuard/WireGuard/UI/iOS/ViewController/SettingsTableViewController.swift index ff83b2c..9956b7b 100644 --- a/WireGuard/WireGuard/UI/iOS/ViewController/SettingsTableViewController.swift +++ b/WireGuard/WireGuard/UI/iOS/ViewController/SettingsTableViewController.swift @@ -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 } |