forked from shadowfacts/Tusker
Don't fail decoding when one status fails to decode
Also remove old workaround for bad dates from #477 Closes #478
This commit is contained in:
parent
e6d9a33dbf
commit
c2232a5e14
|
@ -42,8 +42,7 @@ public struct Client: Sendable {
|
|||
} else if let date = iso8601.date(from: str) {
|
||||
return date
|
||||
} else {
|
||||
// throw DecodingError.typeMismatch(Date.self, .init(codingPath: container.codingPath, debugDescription: "unexpected date format: \(str)"))
|
||||
return Date(timeIntervalSinceReferenceDate: 0)
|
||||
throw DecodingError.typeMismatch(Date.self, .init(codingPath: container.codingPath, debugDescription: "unexpected date format: \(str)"))
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -205,8 +204,8 @@ public struct Client: Sendable {
|
|||
return Request<Account>(method: .get, path: "/api/v1/accounts/verify_credentials")
|
||||
}
|
||||
|
||||
public static func getFavourites(range: RequestRange = .default) -> Request<[Status]> {
|
||||
var request = Request<[Status]>(method: .get, path: "/api/v1/favourites")
|
||||
public static func getFavourites(range: RequestRange = .default) -> Request<[TryDecode<Status>]> {
|
||||
var request = Request<[TryDecode<Status>]>(method: .get, path: "/api/v1/favourites")
|
||||
request.range = range
|
||||
return request
|
||||
}
|
||||
|
@ -457,14 +456,13 @@ public struct Client: Sendable {
|
|||
}
|
||||
|
||||
// MARK: - Timelines
|
||||
public static func getStatuses(timeline: Timeline, range: RequestRange = .default) -> Request<[Status]> {
|
||||
public static func getStatuses(timeline: Timeline, range: RequestRange = .default) -> Request<[TryDecode<Status>]> {
|
||||
return timeline.request(range: range)
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Bookmarks
|
||||
public static func getBookmarks(range: RequestRange = .default) -> Request<[Status]> {
|
||||
var request = Request<[Status]>(method: .get, path: "/api/v1/bookmarks")
|
||||
public static func getBookmarks(range: RequestRange = .default) -> Request<[TryDecode<Status>]> {
|
||||
var request = Request<[TryDecode<Status>]>(method: .get, path: "/api/v1/bookmarks")
|
||||
request.range = range
|
||||
return request
|
||||
}
|
||||
|
@ -492,7 +490,7 @@ public struct Client: Sendable {
|
|||
return Request<[Hashtag]>(method: .get, path: "/api/v1/trends/tags", queryParameters: parameters)
|
||||
}
|
||||
|
||||
public static func getTrendingStatuses(limit: Int? = nil, offset: Int? = nil) -> Request<[Status]> {
|
||||
public static func getTrendingStatuses(limit: Int? = nil, offset: Int? = nil) -> Request<[TryDecode<Status>]> {
|
||||
var parameters: [Parameter] = []
|
||||
if let limit {
|
||||
parameters.append("limit" => limit)
|
||||
|
|
|
@ -95,8 +95,8 @@ public final class Account: AccountProtocol, Decodable, Sendable {
|
|||
return request
|
||||
}
|
||||
|
||||
public static func getStatuses(_ accountID: String, range: RequestRange = .default, onlyMedia: Bool? = nil, pinned: Bool? = nil, excludeReplies: Bool? = nil, excludeReblogs: Bool? = nil) -> Request<[Status]> {
|
||||
var request = Request<[Status]>(method: .get, path: "/api/v1/accounts/\(accountID)/statuses", queryParameters: [
|
||||
public static func getStatuses(_ accountID: String, range: RequestRange = .default, onlyMedia: Bool? = nil, pinned: Bool? = nil, excludeReplies: Bool? = nil, excludeReblogs: Bool? = nil) -> Request<[TryDecode<Status>]> {
|
||||
var request = Request<[TryDecode<Status>]>(method: .get, path: "/api/v1/accounts/\(accountID)/statuses", queryParameters: [
|
||||
"only_media" => onlyMedia,
|
||||
"pinned" => pinned,
|
||||
"exclude_replies" => excludeReplies,
|
||||
|
|
|
@ -10,7 +10,7 @@ import Foundation
|
|||
|
||||
public struct SearchResults: Decodable, Sendable {
|
||||
public let accounts: [Account]
|
||||
public let statuses: [Status]
|
||||
public let statuses: [TryDecode<Status>]
|
||||
public let hashtags: [Hashtag]
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
|
|
|
@ -32,8 +32,8 @@ extension Timeline {
|
|||
}
|
||||
}
|
||||
|
||||
func request(range: RequestRange) -> Request<[Status]> {
|
||||
var request: Request<[Status]> = Request<[Status]>(method: .get, path: endpoint)
|
||||
func request(range: RequestRange) -> Request<[TryDecode<Status>]> {
|
||||
var request = Request<[TryDecode<Status>]>(method: .get, path: endpoint)
|
||||
if case .public(true) = self {
|
||||
request.queryParameters.append("local" => true)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
//
|
||||
// TryDecode.swift
|
||||
// Pachyderm
|
||||
//
|
||||
// Created by Shadowfacts on 6/8/24.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public enum TryDecode<T: Decodable>: Decodable {
|
||||
case error(String)
|
||||
case value(T)
|
||||
|
||||
public init(from decoder: any Decoder) throws {
|
||||
do {
|
||||
self = .value(try T(from: decoder))
|
||||
} catch {
|
||||
self = .error(error.localizedDescription)
|
||||
}
|
||||
}
|
||||
|
||||
public var value: T? {
|
||||
if case .value(let value) = self {
|
||||
value
|
||||
} else {
|
||||
nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension TryDecode: Sendable where T: Sendable {
|
||||
}
|
|
@ -232,7 +232,7 @@ class ConversationViewController: UIViewController {
|
|||
let request = Client.search(query: effectiveURL, types: [.statuses], resolve: true)
|
||||
do {
|
||||
let (results, _) = try await mastodonController.run(request)
|
||||
guard let status = results.statuses.first(where: { $0.url?.serialized() == effectiveURL }) else {
|
||||
guard let status = results.statuses.compactMap(\.value).first(where: { $0.url?.serialized() == effectiveURL }) else {
|
||||
throw UnableToResolveError()
|
||||
}
|
||||
_ = mastodonController.persistentContainer.addOrUpdateOnViewContext(status: status)
|
||||
|
|
|
@ -123,7 +123,7 @@ class TrendingStatusesViewController: UIViewController, CollectionViewController
|
|||
private func loadTrendingStatuses() async {
|
||||
let statuses: [Status]
|
||||
do {
|
||||
statuses = try await mastodonController.run(Client.getTrendingStatuses()).0
|
||||
statuses = try await mastodonController.run(Client.getTrendingStatuses()).0.compactMap(\.value)
|
||||
} catch {
|
||||
let snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
|
||||
await MainActor.run {
|
||||
|
|
|
@ -277,7 +277,7 @@ class TrendsViewController: UIViewController, CollectionViewController {
|
|||
let linksReq = Client.getTrendingLinks(limit: 10)
|
||||
async let links = try? mastodonController.run(linksReq).0
|
||||
let statusesReq = Client.getTrendingStatuses(limit: 10)
|
||||
async let statuses = try? mastodonController.run(statusesReq).0
|
||||
async let statuses = try? mastodonController.run(statusesReq).0.compactMap(\.value)
|
||||
|
||||
if let links = await links {
|
||||
if snapshot.sectionIdentifiers.contains(.profileSuggestions) {
|
||||
|
@ -332,7 +332,7 @@ class TrendsViewController: UIViewController, CollectionViewController {
|
|||
|
||||
do {
|
||||
let request = Client.getTrendingStatuses(offset: origSnapshot.itemIdentifiers(inSection: .trendingStatuses).count)
|
||||
let (statuses, _) = try await mastodonController.run(request)
|
||||
let statuses = try await mastodonController.run(request).0.compactMap(\.value)
|
||||
|
||||
await mastodonController.persistentContainer.addAll(statuses: statuses)
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ class LocalPredicateStatusesViewController: UIViewController, CollectionViewCont
|
|||
let mastodonController: MastodonController
|
||||
private let predicate: (StatusMO) -> Bool
|
||||
private let predicateTitle: String
|
||||
private let request: (RequestRange) -> Request<[Status]>
|
||||
private let request: (RequestRange) -> Request<[TryDecode<Status>]>
|
||||
|
||||
var collectionView: UICollectionView! {
|
||||
view as? UICollectionView
|
||||
|
@ -28,7 +28,7 @@ class LocalPredicateStatusesViewController: UIViewController, CollectionViewCont
|
|||
private var newer: RequestRange?
|
||||
private var older: RequestRange?
|
||||
|
||||
init(predicate: @escaping (StatusMO) -> Bool, predicateTitle: String, request: @escaping (RequestRange) -> Request<[Status]>, mastodonController: MastodonController) {
|
||||
init(predicate: @escaping (StatusMO) -> Bool, predicateTitle: String, request: @escaping (RequestRange) -> Request<[TryDecode<Status>]>, mastodonController: MastodonController) {
|
||||
self.mastodonController = mastodonController
|
||||
self.predicate = predicate
|
||||
self.predicateTitle = predicateTitle
|
||||
|
@ -140,7 +140,8 @@ class LocalPredicateStatusesViewController: UIViewController, CollectionViewCont
|
|||
|
||||
do {
|
||||
let req = request(.count(Self.pageSize))
|
||||
let (statuses, pagination) = try await mastodonController.run(req)
|
||||
let (tryStatuses, pagination) = try await mastodonController.run(req)
|
||||
let statuses = tryStatuses.compactMap(\.value)
|
||||
newer = pagination?.newer
|
||||
older = pagination?.older
|
||||
|
||||
|
@ -180,7 +181,8 @@ class LocalPredicateStatusesViewController: UIViewController, CollectionViewCont
|
|||
|
||||
do {
|
||||
let req = request(older.withCount(Self.pageSize))
|
||||
let (statuses, pagination) = try await mastodonController.run(req)
|
||||
let (tryStatuses, pagination) = try await mastodonController.run(req)
|
||||
let statuses = tryStatuses.compactMap(\.value)
|
||||
self.older = pagination?.older
|
||||
|
||||
await mastodonController.persistentContainer.addAll(statuses: statuses)
|
||||
|
@ -278,7 +280,8 @@ class LocalPredicateStatusesViewController: UIViewController, CollectionViewCont
|
|||
Task {
|
||||
do {
|
||||
let req = request(newer.withCount(Self.pageSize))
|
||||
let (statuses, pagination) = try await mastodonController.run(req)
|
||||
let (tryStatuses, pagination) = try await mastodonController.run(req)
|
||||
let statuses = tryStatuses.compactMap(\.value)
|
||||
self.newer = pagination?.newer
|
||||
|
||||
await mastodonController.persistentContainer.addAll(statuses: statuses)
|
||||
|
|
|
@ -310,7 +310,7 @@ class ProfileStatusesViewController: UIViewController, TimelineLikeCollectionVie
|
|||
}
|
||||
|
||||
let request = Account.getStatuses(accountID, range: .default, onlyMedia: false, pinned: true, excludeReplies: false)
|
||||
let (statuses, _) = try await mastodonController.run(request)
|
||||
let statuses = try await mastodonController.run(request).0.compactMap(\.value)
|
||||
|
||||
await withCheckedContinuation { continuation in
|
||||
mastodonController.persistentContainer.addAll(statuses: statuses) {
|
||||
|
@ -526,7 +526,7 @@ extension ProfileStatusesViewController {
|
|||
extension ProfileStatusesViewController: TimelineLikeControllerDataSource {
|
||||
typealias TimelineItem = String // status ID
|
||||
|
||||
private func request(for range: RequestRange = .default) -> Request<[Status]> {
|
||||
private func request(for range: RequestRange = .default) -> Request<[TryDecode<Status>]> {
|
||||
switch kind {
|
||||
case .statuses:
|
||||
return Account.getStatuses(accountID, range: range, onlyMedia: false, pinned: false, excludeReplies: true)
|
||||
|
@ -539,7 +539,7 @@ extension ProfileStatusesViewController: TimelineLikeControllerDataSource {
|
|||
|
||||
func loadInitial() async throws -> [String] {
|
||||
let request = request()
|
||||
let (statuses, _) = try await mastodonController.run(request)
|
||||
let statuses = try await mastodonController.run(request).0.compactMap(\.value)
|
||||
|
||||
if !statuses.isEmpty {
|
||||
newer = .after(id: statuses.first!.id, count: nil)
|
||||
|
@ -559,7 +559,7 @@ extension ProfileStatusesViewController: TimelineLikeControllerDataSource {
|
|||
}
|
||||
|
||||
let request = request(for: newer)
|
||||
let (statuses, _) = try await mastodonController.run(request)
|
||||
let statuses = try await mastodonController.run(request).0.compactMap(\.value)
|
||||
|
||||
guard !statuses.isEmpty else {
|
||||
throw Error.allCaughtUp
|
||||
|
@ -580,7 +580,7 @@ extension ProfileStatusesViewController: TimelineLikeControllerDataSource {
|
|||
}
|
||||
|
||||
let request = request(for: older)
|
||||
let (statuses, _) = try await mastodonController.run(request)
|
||||
let statuses = try await mastodonController.run(request).0.compactMap(\.value)
|
||||
|
||||
guard !statuses.isEmpty else {
|
||||
return []
|
||||
|
|
|
@ -53,7 +53,7 @@ struct ReportAddStatusView: View {
|
|||
.task { @MainActor in
|
||||
do {
|
||||
let req = Account.getStatuses(report.accountID, range: .count(40), excludeReplies: false, excludeReblogs: true)
|
||||
let (statuses, _) = try await mastodonController.run(req)
|
||||
let statuses = try await mastodonController.run(req).0.compactMap(\.value)
|
||||
await mastodonController.persistentContainer.addAll(statuses: statuses)
|
||||
self.statuses = statuses.compactMap { mastodonController.persistentContainer.status(for: $0.id) }
|
||||
} catch {
|
||||
|
|
|
@ -266,7 +266,7 @@ class SearchResultsViewController: UIViewController, CollectionViewController {
|
|||
guard self.currentQuery == query else { return }
|
||||
self.mastodonController.persistentContainer.performBatchUpdates { (context, addAccounts, addStatuses) in
|
||||
addAccounts(results.accounts)
|
||||
addStatuses(results.statuses)
|
||||
addStatuses(results.statuses.compactMap(\.value))
|
||||
} completion: {
|
||||
DispatchQueue.main.async {
|
||||
self.showSearchResults(results)
|
||||
|
@ -299,7 +299,7 @@ class SearchResultsViewController: UIViewController, CollectionViewController {
|
|||
}
|
||||
if !results.statuses.isEmpty && resultTypes.contains(.statuses) {
|
||||
snapshot.appendSections([.statuses])
|
||||
snapshot.appendItems(results.statuses.map { .status($0.id, .unknown) }, toSection: .statuses)
|
||||
snapshot.appendItems(results.statuses.compactMap(\.value).map { .status($0.id, .unknown) }, toSection: .statuses)
|
||||
}
|
||||
|
||||
dataSource.apply(snapshot)
|
||||
|
|
|
@ -565,7 +565,8 @@ class TimelineViewController: UIViewController, TimelineLikeCollectionViewContro
|
|||
do {
|
||||
let (marker, _) = try await mastodonController.run(TimelineMarkers.request(timeline: .home))
|
||||
async let status = try await mastodonController.run(Client.getStatus(id: marker.lastReadID)).0
|
||||
async let olderStatuses = try await mastodonController.run(Client.getStatuses(timeline: .home, range: .before(id: marker.lastReadID, count: Self.pageSize))).0
|
||||
// TODO: consider replacing undecodable statuses here with items to indicate that to the user
|
||||
async let olderStatuses = try await mastodonController.run(Client.getStatuses(timeline: .home, range: .before(id: marker.lastReadID, count: Self.pageSize))).0.compactMap(\.value)
|
||||
|
||||
let allStatuses = try await [status] + olderStatuses
|
||||
await mastodonController.persistentContainer.addAll(statuses: allStatuses)
|
||||
|
@ -1100,7 +1101,7 @@ extension TimelineViewController: TimelineLikeControllerDataSource {
|
|||
|
||||
func loadInitial() async throws -> [TimelineItem] {
|
||||
let request = Client.getStatuses(timeline: timeline, range: .count(TimelineViewController.pageSize))
|
||||
let (statuses, _) = try await mastodonController.run(request)
|
||||
let statuses = try await mastodonController.run(request).0.compactMap(\.value)
|
||||
|
||||
await withCheckedContinuation { continuation in
|
||||
mastodonController.persistentContainer.addAll(statuses: statuses) {
|
||||
|
@ -1119,7 +1120,7 @@ extension TimelineViewController: TimelineLikeControllerDataSource {
|
|||
let newer = RequestRange.after(id: id, count: TimelineViewController.pageSize)
|
||||
|
||||
let request = Client.getStatuses(timeline: timeline, range: newer)
|
||||
let (statuses, _) = try await mastodonController.run(request)
|
||||
let statuses = try await mastodonController.run(request).0.compactMap(\.value)
|
||||
|
||||
guard !statuses.isEmpty else {
|
||||
throw TimelineViewController.Error.allCaughtUp
|
||||
|
@ -1143,7 +1144,7 @@ extension TimelineViewController: TimelineLikeControllerDataSource {
|
|||
let older = RequestRange.before(id: id, count: TimelineViewController.pageSize)
|
||||
|
||||
let request = Client.getStatuses(timeline: timeline, range: older)
|
||||
let (statuses, _) = try await mastodonController.run(request)
|
||||
let statuses = try await mastodonController.run(request).0.compactMap(\.value)
|
||||
|
||||
guard !statuses.isEmpty else {
|
||||
return []
|
||||
|
@ -1181,7 +1182,7 @@ extension TimelineViewController: TimelineLikeControllerDataSource {
|
|||
}
|
||||
|
||||
let request = Client.getStatuses(timeline: timeline, range: range)
|
||||
let (statuses, _) = try await mastodonController.run(request)
|
||||
let statuses = try await mastodonController.run(request).0.compactMap(\.value)
|
||||
|
||||
guard !statuses.isEmpty else {
|
||||
return []
|
||||
|
|
Loading…
Reference in New Issue