diff --git a/Tusker.xcodeproj/project.pbxproj b/Tusker.xcodeproj/project.pbxproj index 03a5762e..2d31267b 100644 --- a/Tusker.xcodeproj/project.pbxproj +++ b/Tusker.xcodeproj/project.pbxproj @@ -42,6 +42,7 @@ D61AC1D5232E9FA600C54D2D /* InstanceSelectorTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61AC1D4232E9FA600C54D2D /* InstanceSelectorTableViewController.swift */; }; D61AC1D8232EA42D00C54D2D /* InstanceTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61AC1D6232EA42D00C54D2D /* InstanceTableViewCell.swift */; }; D61AC1D9232EA42D00C54D2D /* InstanceTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D61AC1D7232EA42D00C54D2D /* InstanceTableViewCell.xib */; }; + D61DC84628F498F200B82C6E /* Logging.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61DC84528F498F200B82C6E /* Logging.swift */; }; D620483423D3801D008A63EF /* LinkTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D620483323D3801D008A63EF /* LinkTextView.swift */; }; D620483623D38075008A63EF /* ContentTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D620483523D38075008A63EF /* ContentTextView.swift */; }; D620483823D38190008A63EF /* StatusContentTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D620483723D38190008A63EF /* StatusContentTextView.swift */; }; @@ -394,6 +395,7 @@ D61AC1D4232E9FA600C54D2D /* InstanceSelectorTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstanceSelectorTableViewController.swift; sourceTree = ""; }; D61AC1D6232EA42D00C54D2D /* InstanceTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstanceTableViewCell.swift; sourceTree = ""; }; D61AC1D7232EA42D00C54D2D /* InstanceTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = InstanceTableViewCell.xib; sourceTree = ""; }; + D61DC84528F498F200B82C6E /* Logging.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logging.swift; sourceTree = ""; }; D620483323D3801D008A63EF /* LinkTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkTextView.swift; sourceTree = ""; }; D620483523D38075008A63EF /* ContentTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentTextView.swift; sourceTree = ""; }; D620483723D38190008A63EF /* StatusContentTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusContentTextView.swift; sourceTree = ""; }; @@ -1376,6 +1378,7 @@ D6B30E08254BAF63009CAEE5 /* ImageGrayscalifier.swift */, D60E2F2B24423EAD005F8713 /* LazilyDecoding.swift */, D64D0AAC2128D88B005A6F37 /* LocalData.swift */, + D61DC84528F498F200B82C6E /* Logging.swift */, D6AC956623C4347E008C9946 /* MainSceneDelegate.swift */, D6B81F432560390300F6E31D /* MenuController.swift */, D64D8CA82463B494006B0BAA /* MultiThreadDictionary.swift */, @@ -1846,6 +1849,7 @@ D6114E0D27F7FEB30080E273 /* TrendingStatusesViewController.swift in Sources */, D6BC9DDA232D8BE5002CA326 /* SearchResultsViewController.swift in Sources */, D61ABEF628EE74D400B29151 /* StatusCollectionViewCell.swift in Sources */, + D61DC84628F498F200B82C6E /* Logging.swift in Sources */, D627FF7F217E95E000CC0648 /* DraftTableViewCell.swift in Sources */, D6B17255254F88B800128392 /* OppositeCollapseKeywordsView.swift in Sources */, D6A00B1D26379FC900316AD4 /* PollOptionsView.swift in Sources */, diff --git a/Tusker/Logging.swift b/Tusker/Logging.swift new file mode 100644 index 00000000..841335c4 --- /dev/null +++ b/Tusker/Logging.swift @@ -0,0 +1,42 @@ +// +// Logging.swift +// Tusker +// +// Created by Shadowfacts on 10/10/22. +// Copyright © 2022 Shadowfacts. All rights reserved. +// + +import Foundation +import OSLog + +struct Logging { + private init() {} + + static let general = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "General") + + static func getLogData() -> Data? { + do { + let store = try OSLogStore(scope: .currentProcessIdentifier) + // past hour + let position = store.position(date: Date().addingTimeInterval(-60 * 60)) + let entries = try store.getEntries(at: position, matching: NSPredicate(format: "subsystem = %@", Bundle.main.bundleIdentifier!)) + var data = Data() + for entry in entries { + guard let entry = entry as? OSLogEntryLog else { + continue + } + data.append(contentsOf: entry.date.formatted(.iso8601).utf8) + data.append(32) // ' ' + data.append(91) // '[' + data.append(contentsOf: entry.category.utf8) + data.append(93) // ']' + data.append(32) // ' ' + data.append(contentsOf: entry.composedMessage.utf8) + data.append(10) // '\n' + } + return data + } catch { + return nil + } + } +} diff --git a/Tusker/Screens/Crash Reporter/IssueReporterViewController.swift b/Tusker/Screens/Crash Reporter/IssueReporterViewController.swift index 9b7e60eb..2b777704 100644 --- a/Tusker/Screens/Crash Reporter/IssueReporterViewController.swift +++ b/Tusker/Screens/Crash Reporter/IssueReporterViewController.swift @@ -48,7 +48,7 @@ class IssueReporterViewController: UIViewController { self.logDataTask = Task(priority: .userInitiated) { return await withCheckedContinuation({ continuation in DispatchQueue.global().async { - continuation.resume(returning: getLogData()) + continuation.resume(returning: Logging.getLogData()) } }) } @@ -158,29 +158,3 @@ extension IssueReporterViewController: MFMailComposeViewControllerDelegate { } } } - -fileprivate func getLogData() -> Data? { - do { - let store = try OSLogStore(scope: .currentProcessIdentifier) - // past hour - let position = store.position(date: Date().addingTimeInterval(-60 * 60)) - let entries = try store.getEntries(at: position, matching: NSPredicate(format: "subsystem = %@", Bundle.main.bundleIdentifier!)) - var data = Data() - for entry in entries { - guard let entry = entry as? OSLogEntryLog else { - continue - } - data.append(contentsOf: entry.date.formatted(.iso8601).utf8) - data.append(32) // ' ' - data.append(91) // '[' - data.append(contentsOf: entry.category.utf8) - data.append(93) // ']' - data.append(32) // ' ' - data.append(contentsOf: entry.composedMessage.utf8) - data.append(10) // '\n' - } - return data - } catch { - return nil - } -} diff --git a/Tusker/Screens/Utilities/Previewing.swift b/Tusker/Screens/Utilities/Previewing.swift index 4ac58031..207144e7 100644 --- a/Tusker/Screens/Utilities/Previewing.swift +++ b/Tusker/Screens/Utilities/Previewing.swift @@ -301,13 +301,16 @@ extension MenuActionProvider { }), at: 0) } - var shareSection = [ - openInSafariAction(url: status.url!), - createAction(identifier: "share", title: "Share", systemImageName: "square.and.arrow.up", handler: { [weak self, weak sourceView] (_) in - guard let self = self else { return } - self.navigationDelegate?.showMoreOptions(forStatus: status.id, sourceView: sourceView) - }), - ] + var shareSection: [UIAction] = [] + if let url = status.url { + shareSection.append(openInSafariAction(url: url)) + } else { + Logging.general.fault("Status missing URL: id=\(status.id, privacy: .public), reblog=\((status.reblog?.id).debugDescription, privacy: .public)") + } + shareSection.append(createAction(identifier: "share", title: "Share", systemImageName: "square.and.arrow.up", handler: { [weak self, weak sourceView] (_) in + guard let self = self else { return } + self.navigationDelegate?.showMoreOptions(forStatus: status.id, sourceView: sourceView) + })) addOpenInNewWindow(actions: &shareSection, activity: UserActivityManager.showConversationActivity(mainStatusID: status.id, accountID: accountID)) diff --git a/Tusker/TuskerNavigationDelegate.swift b/Tusker/TuskerNavigationDelegate.swift index 917736eb..14e334a4 100644 --- a/Tusker/TuskerNavigationDelegate.swift +++ b/Tusker/TuskerNavigationDelegate.swift @@ -140,7 +140,10 @@ extension TuskerNavigationDelegate { private func moreOptions(forStatus statusID: String) -> UIActivityViewController { guard let status = apiController.persistentContainer.status(for: statusID) else { fatalError("Missing cached status \(statusID)") } - guard let url = status.url else { fatalError("Missing url for status \(statusID)") } + guard let url = status.url else { + Logging.general.fault("Status missing URL: id=\(status.id, privacy: .public), reblog=\((status.reblog?.id).debugDescription, privacy: .public)") + fatalError("Cannot create UIActivityViewController for status without URL") + } return UIActivityViewController(activityItems: [url, StatusActivityItemSource(status)], applicationActivities: nil) }