Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
signalapp
GitHub Repository: signalapp/Signal-iOS
Path: blob/main/Signal/src/ViewControllers/AppSettings/Internal/LogViewController.swift
1 views
//
// Copyright 2019 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//

import SignalServiceKit
public import SignalUI

public class LogPickerViewController: OWSTableViewController2 {
    let logDirUrl: URL

    public init(logDirUrl: URL) {
        self.logDirUrl = logDirUrl
        super.init()
    }

    override public func viewDidLoad() {
        super.viewDidLoad()
        updateTableContents()
    }

    public func updateTableContents() {
        let contents = OWSTableContents()
        contents.add(buildPreferenceSection())
        contents.add(buildActionsSection())
        contents.add(buildLogsSection())
        self.contents = contents
    }

    private func buildPreferenceSection() -> OWSTableSection {
        let chooChoo = OWSTableItem.switch(
            withText: "🚂 Play Sound When Errors Occur",
            isOn: { Preferences.isAudibleErrorLoggingEnabled },
            target: self,
            selector: #selector(didToggleAudiblePreference(_:)),
        )
        let failDebug = OWSTableItem.switch(
            withText: "💥 Crash When owsFailDebugs Occur",
            isOn: { Preferences.isFailDebugEnabled },
            target: self,
            selector: #selector(didToggleFailDebug(_:)),
        )
        return OWSTableSection(title: "Preferences", items: [chooChoo, failDebug])
    }

    private func buildActionsSection() -> OWSTableSection {
        let exportItem = OWSTableItem(title: "Export Logs") {
            Logger.flush()
            DebugLogs.exportLogs()
        }
        return OWSTableSection(title: "Actions", items: [exportItem])
    }

    private func buildLogsSection() -> OWSTableSection {
        guard let directoryEnumerator = FileManager.default.enumerator(at: logDirUrl, includingPropertiesForKeys: nil) else {
            owsFailDebug("logUrls was unexpectedly nil")
            return OWSTableSection(title: "No Log URLs", items: [])
        }

        let logUrls: [URL] = directoryEnumerator.compactMap { $0 as? URL }
        let sortedUrls = logUrls.sorted { a, b -> Bool in
            return a.lastPathComponent > b.lastPathComponent
        }

        let logItems: [OWSTableItem] = sortedUrls.map { logUrl in
            return OWSTableItem(
                customCellBlock: { () -> UITableViewCell in
                    let cell = OWSTableItem.newCell()
                    guard let textLabel = cell.textLabel else {
                        owsFailDebug("textLabel was unexpectedly nil")
                        return cell
                    }
                    textLabel.lineBreakMode = .byTruncatingHead
                    textLabel.text = logUrl.lastPathComponent

                    return cell
                },
                actionBlock: { [weak self] in
                    guard let self else { return }
                    let logVC = LogViewController(logUrl: logUrl)

                    guard let navigationController = self.navigationController else {
                        owsFailDebug("navigationController was unexpectedly nil")
                        return
                    }

                    navigationController.pushViewController(logVC, animated: true)
                },
            )
        }

        return OWSTableSection(title: "View Logs", items: logItems)
    }

    @objc
    private func didToggleAudiblePreference(_ sender: UISwitch) {
        Preferences.setIsAudibleErrorLoggingEnabled(sender.isOn)
        if sender.isOn {
            ErrorLogger.playAlertSound()
        }
    }

    @objc
    private func didToggleFailDebug(_ sender: UISwitch) {
        Preferences.setIsFailDebugEnabled(sender.isOn)
    }
}

public class LogViewController: UIViewController {

    let logUrl: URL

    public init(logUrl: URL) {
        self.logUrl = logUrl
        super.init(nibName: nil, bundle: nil)
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    let textView = UITextView()

    override public func loadView() {
        self.view = textView
        loadLogText()
    }

    override public func viewDidLoad() {
        super.viewDidLoad()
        navigationItem.rightBarButtonItems = [
            UIBarButtonItem(image: Theme.iconImage(.buttonShare), style: .plain, target: self, action: #selector(didTapShare(_:))),
            UIBarButtonItem(image: Theme.iconImage(.buttonDelete), style: .plain, target: self, action: #selector(didTapTrash(_:))),
        ]
    }

    func loadLogText() {
        do {
            // This is super crude, but:
            // 1. generally we should haven't a ton of logged errors
            // 2. this is a dev tool
            let logData = try Data(contentsOf: logUrl)

            // TODO most recent lines on top?
            textView.text = String(data: logData, encoding: .utf8)
        } catch {
            textView.text = "Failed to load log data: \(error)"
        }
    }

    @objc
    private func didTapTrash(_ sender: UIBarButtonItem) {
        // truncate logUrl
        do {
            try NSData().write(to: logUrl)
            loadLogText()
        } catch {
            owsFailDebug("error: \(error)")
        }
    }

    @objc
    private func didTapShare(_ sender: UIBarButtonItem) {
        let logText = textView.text ?? "Empty Log"
        let vc = UIActivityViewController(activityItems: [logText], applicationActivities: [])
        present(vc, animated: true)
    }
}