Account list db sync and pagination

This commit is contained in:
Justin Mazzocchi 2020-09-28 15:40:03 -07:00
parent 8b6a521db1
commit a9e5bb7ef3
No known key found for this signature in database
GPG key ID: E223E6937AAFB01C
4 changed files with 75 additions and 11 deletions

View file

@ -0,0 +1,27 @@
// Copyright © 2020 Metabolist. All rights reserved.
import Foundation
import GRDB
public struct AccountList: Codable, FetchableRecord, PersistableRecord {
let id: UUID
public init() {
id = UUID()
}
}
extension AccountList {
static let joins = hasMany(
AccountListJoin.self,
using: ForeignKey([Column("listId")]))
.order(Column("index"))
static let accounts = hasMany(
AccountRecord.self,
through: joins,
using: AccountListJoin.account)
var accounts: QueryInterfaceRequest<AccountResult> {
request(for: Self.accounts).accountResultRequest
}
}

View file

@ -0,0 +1,12 @@
// Copyright © 2020 Metabolist. All rights reserved.
import Foundation
import GRDB
struct AccountListJoin: Codable, FetchableRecord, PersistableRecord {
let accountId: String
let listId: UUID
let index: Int
static let account = belongsTo(AccountRecord.self, using: ForeignKey([Column("accountId")]))
}

View file

@ -115,10 +115,15 @@ public extension ContentDatabase {
.eraseToAnyPublisher() .eraseToAnyPublisher()
} }
func insert(accounts: [Account]) -> AnyPublisher<Never, Error> { func append(accounts: [Account], toList list: AccountList) -> AnyPublisher<Never, Error> {
databaseWriter.writePublisher { databaseWriter.writePublisher {
for account in accounts { try list.save($0)
let count = try list.accounts.fetchCount($0)
for (index, account) in accounts.enumerated() {
try account.save($0) try account.save($0)
try AccountListJoin(accountId: account.id, listId: list.id, index: count + index).save($0)
} }
} }
.ignoreOutput() .ignoreOutput()
@ -271,6 +276,14 @@ public extension ContentDatabase {
} }
.eraseToAnyPublisher() .eraseToAnyPublisher()
} }
func accountListObservation(_ list: AccountList) -> AnyPublisher<[Account], Error> {
ValueObservation.tracking(list.accounts.fetchAll)
.removeDuplicates()
.publisher(in: databaseWriter)
.map { $0.map(Account.init(result:)) }
.eraseToAnyPublisher()
}
} }
private extension ContentDatabase { private extension ContentDatabase {
@ -390,6 +403,20 @@ private extension ContentDatabase {
t.primaryKey(["accountId", "statusId", "collection"], onConflict: .replace) t.primaryKey(["accountId", "statusId", "collection"], onConflict: .replace)
} }
try db.create(table: "accountList") { t in
t.column("id", .text).primaryKey(onConflict: .replace)
}
try db.create(table: "accountListJoin") { t in
t.column("accountId", .text).indexed().notNull()
.references("accountRecord", column: "id", onDelete: .cascade, onUpdate: .cascade)
t.column("listId", .text).indexed().notNull()
.references("accountList", column: "id", onDelete: .cascade, onUpdate: .cascade)
t.column("index", .integer).notNull()
t.primaryKey(["accountId", "listId"], onConflict: .replace)
}
} }
return migrator return migrator
@ -401,6 +428,7 @@ private extension ContentDatabase {
try StatusContextJoin.deleteAll($0) try StatusContextJoin.deleteAll($0)
try AccountPinnedStatusJoin.deleteAll($0) try AccountPinnedStatusJoin.deleteAll($0)
try AccountStatusJoin.deleteAll($0) try AccountStatusJoin.deleteAll($0)
try AccountList.deleteAll($0)
} completion: { _, _ in } } completion: { _, _ in }
} }
} }

View file

@ -11,6 +11,7 @@ public struct AccountListService {
public let nextPageMaxIDs: AnyPublisher<String?, Never> public let nextPageMaxIDs: AnyPublisher<String?, Never>
public let navigationService: NavigationService public let navigationService: NavigationService
private let list: AccountList
private let mastodonAPIClient: MastodonAPIClient private let mastodonAPIClient: MastodonAPIClient
private let contentDatabase: ContentDatabase private let contentDatabase: ContentDatabase
private let requestClosure: (_ maxID: String?, _ minID: String?) -> AnyPublisher<Never, Error> private let requestClosure: (_ maxID: String?, _ minID: String?) -> AnyPublisher<Never, Error>
@ -18,27 +19,23 @@ public struct AccountListService {
extension AccountListService { extension AccountListService {
init(favoritedByStatusID statusID: String, mastodonAPIClient: MastodonAPIClient, contentDatabase: ContentDatabase) { init(favoritedByStatusID statusID: String, mastodonAPIClient: MastodonAPIClient, contentDatabase: ContentDatabase) {
let accountSectionsSubject = PassthroughSubject<[[Account]], Error>() let list = AccountList()
let nextPageMaxIDsSubject = PassthroughSubject<String?, Never>() let nextPageMaxIDsSubject = PassthroughSubject<String?, Never>()
self.init( self.init(
accountSections: accountSectionsSubject.eraseToAnyPublisher(), accountSections: contentDatabase.accountListObservation(list).map { [$0] }.eraseToAnyPublisher(),
nextPageMaxIDs: nextPageMaxIDsSubject.eraseToAnyPublisher(), nextPageMaxIDs: nextPageMaxIDsSubject.eraseToAnyPublisher(),
navigationService: NavigationService( navigationService: NavigationService(
status: nil, status: nil,
mastodonAPIClient: mastodonAPIClient, mastodonAPIClient: mastodonAPIClient,
contentDatabase: contentDatabase), contentDatabase: contentDatabase),
list: list,
mastodonAPIClient: mastodonAPIClient, mastodonAPIClient: mastodonAPIClient,
contentDatabase: contentDatabase) { maxID, minID -> AnyPublisher<Never, Error> in contentDatabase: contentDatabase) { maxID, minID -> AnyPublisher<Never, Error> in
mastodonAPIClient.pagedRequest( mastodonAPIClient.pagedRequest(
AccountsEndpoint.statusFavouritedBy(id: statusID), maxID: maxID, minID: minID) AccountsEndpoint.statusFavouritedBy(id: statusID), maxID: maxID, minID: minID)
.handleEvents( .handleEvents(receiveOutput: { nextPageMaxIDsSubject.send($0.info.maxID) })
receiveOutput: { .flatMap { contentDatabase.append(accounts: $0.result, toList: list) }
nextPageMaxIDsSubject.send($0.info.maxID)
accountSectionsSubject.send([$0.result])
},
receiveCompletion: accountSectionsSubject.send)
.flatMap { contentDatabase.insert(accounts: $0.result) }
.eraseToAnyPublisher() .eraseToAnyPublisher()
} }
} }