1
0
mirror of https://github.com/mastodon/mastodon-ios.git synced 2025-02-03 18:57:46 +01:00

feat: Implement blocks/mutes pagination using link header

This commit is contained in:
Marcus Kida 2022-11-24 14:21:53 +01:00
parent 9c86dfe166
commit 12cb8cf8d6
No known key found for this signature in database
GPG Key ID: 19FF64E08013CA40
4 changed files with 101 additions and 11 deletions

View File

@ -25,18 +25,27 @@ extension APIService {
@discardableResult @discardableResult
public func getBlocked( public func getBlocked(
authenticationBox: MastodonAuthenticationBox authenticationBox: MastodonAuthenticationBox
) async throws -> Mastodon.Response.Content<[Mastodon.Entity.Account]> {
try await _getBlocked(sinceID: nil, limit: 40, authenticationBox: authenticationBox)
}
private func _getBlocked(
sinceID: Mastodon.Entity.Status.ID?,
limit: Int,
authenticationBox: MastodonAuthenticationBox
) async throws -> Mastodon.Response.Content<[Mastodon.Entity.Account]> { ) async throws -> Mastodon.Response.Content<[Mastodon.Entity.Account]> {
let managedObjectContext = backgroundManagedObjectContext let managedObjectContext = backgroundManagedObjectContext
let response = try await Mastodon.API.Account.blocks( let response = try await Mastodon.API.Account.blocks(
session: session, session: session,
domain: authenticationBox.domain, domain: authenticationBox.domain,
sinceID: sinceID,
limit: limit,
authorization: authenticationBox.userAuthorization authorization: authenticationBox.userAuthorization
).singleOutput() ).singleOutput()
let userIDs = response.value.map { $0.id } let userIDs = response.value.map { $0.id }
let predicate = NSPredicate(format: "%K IN %@", #keyPath(MastodonUser.id), userIDs) let predicate = MastodonUser.predicate(domain: authenticationBox.domain, ids: userIDs)
let fetchRequest = MastodonUser.fetchRequest() let fetchRequest = MastodonUser.fetchRequest()
fetchRequest.predicate = predicate fetchRequest.predicate = predicate
fetchRequest.includesPropertyValues = false fetchRequest.includesPropertyValues = false
@ -49,7 +58,12 @@ extension APIService {
} }
} }
return response /// only try to paginate if retrieved userIDs count is larger than the set limit and if we get a prev linkId that's different than the currently used one
guard userIDs.count == limit, let prevSinceId = response.link?.linkIDs[.linkPrev]?.sinceId, sinceID != prevSinceId else {
return response
}
return try await _getBlocked(sinceID: prevSinceId, limit: limit, authenticationBox: authenticationBox)
} }
public func toggleBlock( public func toggleBlock(

View File

@ -24,18 +24,27 @@ extension APIService {
@discardableResult @discardableResult
public func getMutes( public func getMutes(
authenticationBox: MastodonAuthenticationBox authenticationBox: MastodonAuthenticationBox
) async throws -> Mastodon.Response.Content<[Mastodon.Entity.Account]> {
try await _getMutes(sinceID: nil, limit: 40, authenticationBox: authenticationBox)
}
private func _getMutes(
sinceID: Mastodon.Entity.Status.ID?,
limit: Int,
authenticationBox: MastodonAuthenticationBox
) async throws -> Mastodon.Response.Content<[Mastodon.Entity.Account]> { ) async throws -> Mastodon.Response.Content<[Mastodon.Entity.Account]> {
let managedObjectContext = backgroundManagedObjectContext let managedObjectContext = backgroundManagedObjectContext
let response = try await Mastodon.API.Account.mutes( let response = try await Mastodon.API.Account.mutes(
session: session, session: session,
domain: authenticationBox.domain, domain: authenticationBox.domain,
sinceID: sinceID,
limit: limit,
authorization: authenticationBox.userAuthorization authorization: authenticationBox.userAuthorization
).singleOutput() ).singleOutput()
let userIDs = response.value.map { $0.id } let userIDs = response.value.map { $0.id }
let predicate = MastodonUser.predicate(domain: authenticationBox.domain, ids: userIDs) let predicate = MastodonUser.predicate(domain: authenticationBox.domain, ids: userIDs)
let fetchRequest = MastodonUser.fetchRequest() let fetchRequest = MastodonUser.fetchRequest()
fetchRequest.predicate = predicate fetchRequest.predicate = predicate
fetchRequest.includesPropertyValues = false fetchRequest.includesPropertyValues = false
@ -48,7 +57,12 @@ extension APIService {
} }
} }
return response /// only try to paginate if retrieved userIDs count is larger than the set limit and if we get a prev linkId that's different than the currently used one
guard userIDs.count == limit, let prevSinceId = response.link?.linkIDs[.linkPrev]?.sinceId, sinceID != prevSinceId else {
return response
}
return try await _getMutes(sinceID: prevSinceId, limit: limit, authenticationBox: authenticationBox)
} }
public func toggleMute( public func toggleMute(

View File

@ -239,11 +239,13 @@ public extension Mastodon.API.Account {
static func blocks( static func blocks(
session: URLSession, session: URLSession,
domain: String, domain: String,
sinceID: Mastodon.Entity.Status.ID? = nil,
limit: Int,
authorization: Mastodon.API.OAuth.Authorization authorization: Mastodon.API.OAuth.Authorization
) -> AnyPublisher<Mastodon.Response.Content<[Mastodon.Entity.Account]>, Error> { ) -> AnyPublisher<Mastodon.Response.Content<[Mastodon.Entity.Account]>, Error> {
let request = Mastodon.API.get( let request = Mastodon.API.get(
url: blocksEndpointURL(domain: domain), url: blocksEndpointURL(domain: domain),
query: BlocksQuery(), query: BlocksQuery(sinceID: sinceID, limit: limit),
authorization: authorization authorization: authorization
) )
return session.dataTaskPublisher(for: request) return session.dataTaskPublisher(for: request)
@ -255,8 +257,23 @@ public extension Mastodon.API.Account {
} }
private struct BlocksQuery: GetQuery { private struct BlocksQuery: GetQuery {
private let sinceID: Mastodon.Entity.Status.ID?
private let limit: Int?
public init(
sinceID: Mastodon.Entity.Status.ID?,
limit: Int?
) {
self.sinceID = sinceID
self.limit = limit
}
var queryItems: [URLQueryItem]? { var queryItems: [URLQueryItem]? {
nil var items: [URLQueryItem] = []
sinceID.flatMap { items.append(URLQueryItem(name: "since_id", value: $0)) }
limit.flatMap { items.append(URLQueryItem(name: "limit", value: String($0))) }
guard !items.isEmpty else { return nil }
return items
} }
} }
@ -490,11 +507,13 @@ extension Mastodon.API.Account {
public static func mutes( public static func mutes(
session: URLSession, session: URLSession,
domain: String, domain: String,
sinceID: Mastodon.Entity.Status.ID? = nil,
limit: Int?,
authorization: Mastodon.API.OAuth.Authorization authorization: Mastodon.API.OAuth.Authorization
) -> AnyPublisher<Mastodon.Response.Content<[Mastodon.Entity.Account]>, Error> { ) -> AnyPublisher<Mastodon.Response.Content<[Mastodon.Entity.Account]>, Error> {
let request = Mastodon.API.get( let request = Mastodon.API.get(
url: mutesEndpointURL(domain: domain), url: mutesEndpointURL(domain: domain),
query: MutesQuery(), query: MutesQuery(sinceID: sinceID, limit: limit),
authorization: authorization authorization: authorization
) )
return session.dataTaskPublisher(for: request) return session.dataTaskPublisher(for: request)
@ -505,8 +524,23 @@ extension Mastodon.API.Account {
.eraseToAnyPublisher() .eraseToAnyPublisher()
struct MutesQuery: GetQuery { struct MutesQuery: GetQuery {
private let sinceID: Mastodon.Entity.Status.ID?
private let limit: Int?
public init(
sinceID: Mastodon.Entity.Status.ID?,
limit: Int?
) {
self.sinceID = sinceID
self.limit = limit
}
var queryItems: [URLQueryItem]? { var queryItems: [URLQueryItem]? {
nil var items: [URLQueryItem] = []
sinceID.flatMap { items.append(URLQueryItem(name: "since_id", value: $0)) }
limit.flatMap { items.append(URLQueryItem(name: "limit", value: String($0))) }
guard !items.isEmpty else { return nil }
return items
} }
} }
} }

View File

@ -106,6 +106,7 @@ extension Mastodon.Response {
public struct Link { public struct Link {
public let maxID: Mastodon.Entity.Status.ID? public let maxID: Mastodon.Entity.Status.ID?
public let minID: Mastodon.Entity.Status.ID? public let minID: Mastodon.Entity.Status.ID?
public let linkIDs: [String: Mastodon.Entity.Status.ID]
public let offset: Int? public let offset: Int?
init(link: String) { init(link: String) {
@ -135,6 +136,33 @@ extension Mastodon.Response {
let offset = link[range] let offset = link[range]
return Int(offset) return Int(offset)
}() }()
self.linkIDs = {
var linkIDs = [String: Mastodon.Entity.Status.ID]()
let links = link.components(separatedBy: ", ")
for link in links {
guard let regex = try? NSRegularExpression(pattern: "<(.*)>; *rel=\"(.*)\"") else { return [:] }
let results = regex.matches(in: link, options: [], range: NSRange(link.startIndex..<link.endIndex, in: link))
for match in results {
guard
let labelRange = Range(match.range(at: 2), in: link),
let linkRange = Range(match.range(at: 1), in: link)
else {
continue
}
linkIDs[String(link[labelRange])] = String(link[linkRange])
}
}
return linkIDs
}()
} }
} }
} }
public extension Mastodon.Entity.Status.ID {
static let linkPrev = "prev"
static let linkNext = "next"
var sinceId: String? {
components(separatedBy: "&since_id=").last
}
}