Account list db sync and pagination
This commit is contained in:
parent
8b6a521db1
commit
a9e5bb7ef3
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -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")]))
|
||||||
|
}
|
|
@ -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 }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue