0xDEAD10CC mitigation

This commit is contained in:
Justin Mazzocchi 2021-03-12 15:25:16 -08:00
parent ac2d1fb805
commit 58333b558b
No known key found for this signature in database
GPG key ID: E223E6937AAFB01C
5 changed files with 99 additions and 124 deletions

View file

@ -51,9 +51,7 @@ public extension ContentDatabase {
} }
func insert(status: Status) -> AnyPublisher<Never, Error> { func insert(status: Status) -> AnyPublisher<Never, Error> {
databaseWriter.writePublisher(updates: status.save) databaseWriter.mutatingPublisher(updates: status.save)
.ignoreOutput()
.eraseToAnyPublisher()
} }
// swiftlint:disable:next function_body_length // swiftlint:disable:next function_body_length
@ -61,7 +59,7 @@ public extension ContentDatabase {
statuses: [Status], statuses: [Status],
timeline: Timeline, timeline: Timeline,
loadMoreAndDirection: (LoadMore, LoadMore.Direction)? = nil) -> AnyPublisher<Never, Error> { loadMoreAndDirection: (LoadMore, LoadMore.Direction)? = nil) -> AnyPublisher<Never, Error> {
databaseWriter.writePublisher { databaseWriter.mutatingPublisher {
let timelineRecord = TimelineRecord(timeline: timeline) let timelineRecord = TimelineRecord(timeline: timeline)
try timelineRecord.save($0) try timelineRecord.save($0)
@ -122,12 +120,10 @@ public extension ContentDatabase {
} }
} }
} }
.ignoreOutput()
.eraseToAnyPublisher()
} }
func insert(context: Context, parentId: Status.Id) -> AnyPublisher<Never, Error> { func insert(context: Context, parentId: Status.Id) -> AnyPublisher<Never, Error> {
databaseWriter.writePublisher { databaseWriter.mutatingPublisher {
for (index, status) in context.ancestors.enumerated() { for (index, status) in context.ancestors.enumerated() {
try status.save($0) try status.save($0)
try StatusAncestorJoin(parentId: parentId, statusId: status.id, order: index).save($0) try StatusAncestorJoin(parentId: parentId, statusId: status.id, order: index).save($0)
@ -148,12 +144,10 @@ public extension ContentDatabase {
&& !context.descendants.map(\.id).contains(StatusDescendantJoin.Columns.statusId)) && !context.descendants.map(\.id).contains(StatusDescendantJoin.Columns.statusId))
.deleteAll($0) .deleteAll($0)
} }
.ignoreOutput()
.eraseToAnyPublisher()
} }
func insert(pinnedStatuses: [Status], accountId: Account.Id) -> AnyPublisher<Never, Error> { func insert(pinnedStatuses: [Status], accountId: Account.Id) -> AnyPublisher<Never, Error> {
databaseWriter.writePublisher { databaseWriter.mutatingPublisher {
for (index, status) in pinnedStatuses.enumerated() { for (index, status) in pinnedStatuses.enumerated() {
try status.save($0) try status.save($0)
try AccountPinnedStatusJoin(accountId: accountId, statusId: status.id, order: index).save($0) try AccountPinnedStatusJoin(accountId: accountId, statusId: status.id, order: index).save($0)
@ -164,12 +158,10 @@ public extension ContentDatabase {
&& !pinnedStatuses.map(\.id).contains(AccountPinnedStatusJoin.Columns.statusId)) && !pinnedStatuses.map(\.id).contains(AccountPinnedStatusJoin.Columns.statusId))
.deleteAll($0) .deleteAll($0)
} }
.ignoreOutput()
.eraseToAnyPublisher()
} }
func toggleShowContent(id: Status.Id) -> AnyPublisher<Never, Error> { func toggleShowContent(id: Status.Id) -> AnyPublisher<Never, Error> {
databaseWriter.writePublisher { databaseWriter.mutatingPublisher {
if let toggle = try StatusShowContentToggle if let toggle = try StatusShowContentToggle
.filter(StatusShowContentToggle.Columns.statusId == id) .filter(StatusShowContentToggle.Columns.statusId == id)
.fetchOne($0) { .fetchOne($0) {
@ -178,12 +170,10 @@ public extension ContentDatabase {
try StatusShowContentToggle(statusId: id).save($0) try StatusShowContentToggle(statusId: id).save($0)
} }
} }
.ignoreOutput()
.eraseToAnyPublisher()
} }
func toggleShowAttachments(id: Status.Id) -> AnyPublisher<Never, Error> { func toggleShowAttachments(id: Status.Id) -> AnyPublisher<Never, Error> {
databaseWriter.writePublisher { databaseWriter.mutatingPublisher {
if let toggle = try StatusShowAttachmentsToggle if let toggle = try StatusShowAttachmentsToggle
.filter(StatusShowAttachmentsToggle.Columns.statusId == id) .filter(StatusShowAttachmentsToggle.Columns.statusId == id)
.fetchOne($0) { .fetchOne($0) {
@ -192,23 +182,19 @@ public extension ContentDatabase {
try StatusShowAttachmentsToggle(statusId: id).save($0) try StatusShowAttachmentsToggle(statusId: id).save($0)
} }
} }
.ignoreOutput()
.eraseToAnyPublisher()
} }
func expand(ids: Set<Status.Id>) -> AnyPublisher<Never, Error> { func expand(ids: Set<Status.Id>) -> AnyPublisher<Never, Error> {
databaseWriter.writePublisher { databaseWriter.mutatingPublisher {
for id in ids { for id in ids {
try StatusShowContentToggle(statusId: id).save($0) try StatusShowContentToggle(statusId: id).save($0)
try StatusShowAttachmentsToggle(statusId: id).save($0) try StatusShowAttachmentsToggle(statusId: id).save($0)
} }
} }
.ignoreOutput()
.eraseToAnyPublisher()
} }
func collapse(ids: Set<Status.Id>) -> AnyPublisher<Never, Error> { func collapse(ids: Set<Status.Id>) -> AnyPublisher<Never, Error> {
databaseWriter.writePublisher { databaseWriter.mutatingPublisher {
try StatusShowContentToggle try StatusShowContentToggle
.filter(ids.contains(StatusShowContentToggle.Columns.statusId)) .filter(ids.contains(StatusShowContentToggle.Columns.statusId))
.deleteAll($0) .deleteAll($0)
@ -216,29 +202,23 @@ public extension ContentDatabase {
.filter(ids.contains(StatusShowContentToggle.Columns.statusId)) .filter(ids.contains(StatusShowContentToggle.Columns.statusId))
.deleteAll($0) .deleteAll($0)
} }
.ignoreOutput()
.eraseToAnyPublisher()
} }
func update(id: Status.Id, poll: Poll) -> AnyPublisher<Never, Error> { func update(id: Status.Id, poll: Poll) -> AnyPublisher<Never, Error> {
databaseWriter.writePublisher { databaseWriter.mutatingPublisher {
let data = try StatusRecord.databaseJSONEncoder(for: StatusRecord.Columns.poll.name).encode(poll) let data = try StatusRecord.databaseJSONEncoder(for: StatusRecord.Columns.poll.name).encode(poll)
try StatusRecord.filter(StatusRecord.Columns.id == id) try StatusRecord.filter(StatusRecord.Columns.id == id)
.updateAll($0, StatusRecord.Columns.poll.set(to: data)) .updateAll($0, StatusRecord.Columns.poll.set(to: data))
} }
.ignoreOutput()
.eraseToAnyPublisher()
} }
func delete(id: Status.Id) -> AnyPublisher<Never, Error> { func delete(id: Status.Id) -> AnyPublisher<Never, Error> {
databaseWriter.writePublisher(updates: StatusRecord.filter(StatusRecord.Columns.id == id).deleteAll) databaseWriter.mutatingPublisher(updates: StatusRecord.filter(StatusRecord.Columns.id == id).deleteAll)
.ignoreOutput()
.eraseToAnyPublisher()
} }
func unfollow(id: Account.Id) -> AnyPublisher<Never, Error> { func unfollow(id: Account.Id) -> AnyPublisher<Never, Error> {
databaseWriter.writePublisher { databaseWriter.mutatingPublisher {
let statusIds = try Status.Id.fetchAll( let statusIds = try Status.Id.fetchAll(
$0, $0,
StatusRecord.filter(StatusRecord.Columns.accountId == id).select(StatusRecord.Columns.id)) StatusRecord.filter(StatusRecord.Columns.accountId == id).select(StatusRecord.Columns.id))
@ -248,27 +228,21 @@ public extension ContentDatabase {
&& statusIds.contains(TimelineStatusJoin.Columns.statusId)) && statusIds.contains(TimelineStatusJoin.Columns.statusId))
.deleteAll($0) .deleteAll($0)
} }
.ignoreOutput()
.eraseToAnyPublisher()
} }
func mute(id: Account.Id) -> AnyPublisher<Never, Error> { func mute(id: Account.Id) -> AnyPublisher<Never, Error> {
databaseWriter.writePublisher { databaseWriter.mutatingPublisher {
try StatusRecord.filter(StatusRecord.Columns.accountId == id).deleteAll($0) try StatusRecord.filter(StatusRecord.Columns.accountId == id).deleteAll($0)
try NotificationRecord.filter(NotificationRecord.Columns.accountId == id).deleteAll($0) try NotificationRecord.filter(NotificationRecord.Columns.accountId == id).deleteAll($0)
} }
.ignoreOutput()
.eraseToAnyPublisher()
} }
func block(id: Account.Id) -> AnyPublisher<Never, Error> { func block(id: Account.Id) -> AnyPublisher<Never, Error> {
databaseWriter.writePublisher(updates: AccountRecord.filter(AccountRecord.Columns.id == id).deleteAll) databaseWriter.mutatingPublisher(updates: AccountRecord.filter(AccountRecord.Columns.id == id).deleteAll)
.ignoreOutput()
.eraseToAnyPublisher()
} }
func insert(accounts: [Account], listId: AccountList.Id? = nil) -> AnyPublisher<Never, Error> { func insert(accounts: [Account], listId: AccountList.Id? = nil) -> AnyPublisher<Never, Error> {
databaseWriter.writePublisher { databaseWriter.mutatingPublisher {
var order: Int? var order: Int?
if let listId = listId { if let listId = listId {
@ -290,22 +264,18 @@ public extension ContentDatabase {
} }
} }
} }
.ignoreOutput()
.eraseToAnyPublisher()
} }
func remove(id: Account.Id, from listId: AccountList.Id) -> AnyPublisher<Never, Error> { func remove(id: Account.Id, from listId: AccountList.Id) -> AnyPublisher<Never, Error> {
databaseWriter.writePublisher( databaseWriter.mutatingPublisher(
updates: AccountListJoin.filter( updates: AccountListJoin.filter(
AccountListJoin.Columns.accountId == id AccountListJoin.Columns.accountId == id
&& AccountListJoin.Columns.accountListId == listId) && AccountListJoin.Columns.accountListId == listId)
.deleteAll) .deleteAll)
.ignoreOutput()
.eraseToAnyPublisher()
} }
func insert(identityProofs: [IdentityProof], id: Account.Id) -> AnyPublisher<Never, Error> { func insert(identityProofs: [IdentityProof], id: Account.Id) -> AnyPublisher<Never, Error> {
databaseWriter.writePublisher { databaseWriter.mutatingPublisher {
for identityProof in identityProofs { for identityProof in identityProofs {
try IdentityProofRecord( try IdentityProofRecord(
accountId: id, accountId: id,
@ -317,12 +287,10 @@ public extension ContentDatabase {
.save($0) .save($0)
} }
} }
.ignoreOutput()
.eraseToAnyPublisher()
} }
func insert(featuredTags: [FeaturedTag], id: Account.Id) -> AnyPublisher<Never, Error> { func insert(featuredTags: [FeaturedTag], id: Account.Id) -> AnyPublisher<Never, Error> {
databaseWriter.writePublisher { databaseWriter.mutatingPublisher {
for featuredTag in featuredTags { for featuredTag in featuredTags {
try FeaturedTagRecord( try FeaturedTagRecord(
id: featuredTag.id, id: featuredTag.id,
@ -334,22 +302,18 @@ public extension ContentDatabase {
.save($0) .save($0)
} }
} }
.ignoreOutput()
.eraseToAnyPublisher()
} }
func insert(relationships: [Relationship]) -> AnyPublisher<Never, Error> { func insert(relationships: [Relationship]) -> AnyPublisher<Never, Error> {
databaseWriter.writePublisher { databaseWriter.mutatingPublisher {
for relationship in relationships { for relationship in relationships {
try relationship.save($0) try relationship.save($0)
} }
} }
.ignoreOutput()
.eraseToAnyPublisher()
} }
func setLists(_ lists: [List]) -> AnyPublisher<Never, Error> { func setLists(_ lists: [List]) -> AnyPublisher<Never, Error> {
databaseWriter.writePublisher { databaseWriter.mutatingPublisher {
for list in lists { for list in lists {
try TimelineRecord(timeline: Timeline.list(list)).save($0) try TimelineRecord(timeline: Timeline.list(list)).save($0)
} }
@ -359,86 +323,66 @@ public extension ContentDatabase {
&& TimelineRecord.Columns.listTitle != nil) && TimelineRecord.Columns.listTitle != nil)
.deleteAll($0) .deleteAll($0)
} }
.ignoreOutput()
.eraseToAnyPublisher()
} }
func createList(_ list: List) -> AnyPublisher<Never, Error> { func createList(_ list: List) -> AnyPublisher<Never, Error> {
databaseWriter.writePublisher(updates: TimelineRecord(timeline: Timeline.list(list)).save) databaseWriter.mutatingPublisher(updates: TimelineRecord(timeline: Timeline.list(list)).save)
.ignoreOutput()
.eraseToAnyPublisher()
} }
func deleteList(id: List.Id) -> AnyPublisher<Never, Error> { func deleteList(id: List.Id) -> AnyPublisher<Never, Error> {
databaseWriter.writePublisher(updates: TimelineRecord.filter(TimelineRecord.Columns.listId == id).deleteAll) databaseWriter.mutatingPublisher(updates: TimelineRecord.filter(TimelineRecord.Columns.listId == id).deleteAll)
.ignoreOutput()
.eraseToAnyPublisher()
} }
func setFilters(_ filters: [Filter]) -> AnyPublisher<Never, Error> { func setFilters(_ filters: [Filter]) -> AnyPublisher<Never, Error> {
databaseWriter.writePublisher { databaseWriter.mutatingPublisher {
for filter in filters { for filter in filters {
try filter.save($0) try filter.save($0)
} }
try Filter.filter(!filters.map(\.id).contains(Filter.Columns.id)).deleteAll($0) try Filter.filter(!filters.map(\.id).contains(Filter.Columns.id)).deleteAll($0)
} }
.ignoreOutput()
.eraseToAnyPublisher()
} }
func createFilter(_ filter: Filter) -> AnyPublisher<Never, Error> { func createFilter(_ filter: Filter) -> AnyPublisher<Never, Error> {
databaseWriter.writePublisher(updates: filter.save) databaseWriter.mutatingPublisher(updates: filter.save)
.ignoreOutput()
.eraseToAnyPublisher()
} }
func deleteFilter(id: Filter.Id) -> AnyPublisher<Never, Error> { func deleteFilter(id: Filter.Id) -> AnyPublisher<Never, Error> {
databaseWriter.writePublisher(updates: Filter.filter(Filter.Columns.id == id).deleteAll) databaseWriter.mutatingPublisher(updates: Filter.filter(Filter.Columns.id == id).deleteAll)
.ignoreOutput()
.eraseToAnyPublisher()
} }
func setLastReadId(_ id: String, timelineId: Timeline.Id) -> AnyPublisher<Never, Error> { func setLastReadId(_ id: String, timelineId: Timeline.Id) -> AnyPublisher<Never, Error> {
databaseWriter.writePublisher(updates: LastReadIdRecord(timelineId: timelineId, id: id).save) databaseWriter.mutatingPublisher(updates: LastReadIdRecord(timelineId: timelineId, id: id).save)
.ignoreOutput()
.eraseToAnyPublisher()
} }
func insert(notifications: [MastodonNotification]) -> AnyPublisher<Never, Error> { func insert(notifications: [MastodonNotification]) -> AnyPublisher<Never, Error> {
databaseWriter.writePublisher { databaseWriter.mutatingPublisher {
for notification in notifications { for notification in notifications {
try notification.save($0) try notification.save($0)
} }
} }
.ignoreOutput()
.eraseToAnyPublisher()
} }
func insert(conversations: [Conversation]) -> AnyPublisher<Never, Error> { func insert(conversations: [Conversation]) -> AnyPublisher<Never, Error> {
databaseWriter.writePublisher { databaseWriter.mutatingPublisher {
for conversation in conversations { for conversation in conversations {
try conversation.save($0) try conversation.save($0)
} }
} }
.ignoreOutput()
.eraseToAnyPublisher()
} }
func update(emojis: [Emoji]) -> AnyPublisher<Never, Error> { func update(emojis: [Emoji]) -> AnyPublisher<Never, Error> {
databaseWriter.writePublisher { databaseWriter.mutatingPublisher {
for emoji in emojis { for emoji in emojis {
try emoji.save($0) try emoji.save($0)
} }
try Emoji.filter(!emojis.map(\.shortcode).contains(Emoji.Columns.shortcode)).deleteAll($0) try Emoji.filter(!emojis.map(\.shortcode).contains(Emoji.Columns.shortcode)).deleteAll($0)
} }
.ignoreOutput()
.eraseToAnyPublisher()
} }
func updateUse(emoji: String, system: Bool) -> AnyPublisher<Never, Error> { func updateUse(emoji: String, system: Bool) -> AnyPublisher<Never, Error> {
databaseWriter.writePublisher { databaseWriter.mutatingPublisher {
let count = try Int.fetchOne( let count = try Int.fetchOne(
$0, $0,
EmojiUse.filter(EmojiUse.Columns.system == system && EmojiUse.Columns.emoji == emoji) EmojiUse.filter(EmojiUse.Columns.system == system && EmojiUse.Columns.emoji == emoji)
@ -446,24 +390,20 @@ public extension ContentDatabase {
try EmojiUse(emoji: emoji, system: system, lastUse: Date(), count: (count ?? 0) + 1).save($0) try EmojiUse(emoji: emoji, system: system, lastUse: Date(), count: (count ?? 0) + 1).save($0)
} }
.ignoreOutput()
.eraseToAnyPublisher()
} }
func update(announcements: [Announcement]) -> AnyPublisher<Never, Error> { func update(announcements: [Announcement]) -> AnyPublisher<Never, Error> {
databaseWriter.writePublisher { databaseWriter.mutatingPublisher {
for announcement in announcements { for announcement in announcements {
try announcement.save($0) try announcement.save($0)
} }
try Announcement.filter(!announcements.map(\.id).contains(Announcement.Columns.id)).deleteAll($0) try Announcement.filter(!announcements.map(\.id).contains(Announcement.Columns.id)).deleteAll($0)
} }
.ignoreOutput()
.eraseToAnyPublisher()
} }
func insert(results: Results) -> AnyPublisher<Never, Error> { func insert(results: Results) -> AnyPublisher<Never, Error> {
databaseWriter.writePublisher { databaseWriter.mutatingPublisher {
for account in results.accounts { for account in results.accounts {
try account.save($0) try account.save($0)
} }
@ -472,14 +412,10 @@ public extension ContentDatabase {
try status.save($0) try status.save($0)
} }
} }
.ignoreOutput()
.eraseToAnyPublisher()
} }
func insert(instance: Instance) -> AnyPublisher<Never, Error> { func insert(instance: Instance) -> AnyPublisher<Never, Error> {
databaseWriter.writePublisher(updates: instance.save) databaseWriter.mutatingPublisher(updates: instance.save)
.ignoreOutput()
.eraseToAnyPublisher()
} }
func timelinePublisher(_ timeline: Timeline) -> AnyPublisher<[CollectionSection], Error> { func timelinePublisher(_ timeline: Timeline) -> AnyPublisher<[CollectionSection], Error> {
@ -697,7 +633,6 @@ public extension ContentDatabase {
private extension ContentDatabase { private extension ContentDatabase {
static let cleanAfterLastReadIdCount = 40 static let cleanAfterLastReadIdCount = 40
static let ephemeralTimelines = NSCountedSet() static let ephemeralTimelines = NSCountedSet()
static func fileURL(id: Identity.Id, appGroup: String) throws -> URL { static func fileURL(id: Identity.Id, appGroup: String) throws -> URL {

View file

@ -20,6 +20,7 @@ extension DatabasePool {
configuration.busyMode = .timeout(5) configuration.busyMode = .timeout(5)
configuration.defaultTransactionKind = .immediate configuration.defaultTransactionKind = .immediate
configuration.observesSuspensionNotifications = true
configuration.prepareDatabase { db in configuration.prepareDatabase { db in
try db.usePassphrase(passphrase()) try db.usePassphrase(passphrase())
try db.execute(sql: "PRAGMA cipher_plaintext_header_size = 32") try db.execute(sql: "PRAGMA cipher_plaintext_header_size = 32")

View file

@ -0,0 +1,29 @@
// Copyright © 2021 Metabolist. All rights reserved.
import Combine
import Foundation
import GRDB
// swiftlint:disable:next line_length
// https://github.com/groue/GRDB.swift/blob/master/Documentation/SharingADatabase.md#how-to-limit-the-0xdead10cc-exception
extension DatabaseWriter {
func mutatingPublisher<Output>(updates: @escaping (Database) throws -> Output) -> AnyPublisher<Never, Error> {
let publisher = writePublisher(updates: updates)
return publisher
.tryCatch { error -> AnyPublisher<Output, Error> in
if let databaseError = error as? DatabaseError, databaseError.isInterruptionError {
return NotificationCenter.default.publisher(for: Database.resumeNotification)
.timeout(.seconds(1), scheduler: DispatchQueue.global())
.flatMap { _ in publisher }
.eraseToAnyPublisher()
} else {
throw error
}
}
.retry(1)
.ignoreOutput()
.eraseToAnyPublisher()
}
}

View file

@ -32,7 +32,7 @@ public struct IdentityDatabase {
public extension IdentityDatabase { public extension IdentityDatabase {
func createIdentity(id: Identity.Id, url: URL, authenticated: Bool, pending: Bool) -> AnyPublisher<Never, Error> { func createIdentity(id: Identity.Id, url: URL, authenticated: Bool, pending: Bool) -> AnyPublisher<Never, Error> {
databaseWriter.writePublisher( databaseWriter.mutatingPublisher(
updates: IdentityRecord( updates: IdentityRecord(
id: id, id: id,
url: url, url: url,
@ -44,28 +44,22 @@ public extension IdentityDatabase {
lastRegisteredDeviceToken: nil, lastRegisteredDeviceToken: nil,
pushSubscriptionAlerts: .initial) pushSubscriptionAlerts: .initial)
.save) .save)
.ignoreOutput()
.eraseToAnyPublisher()
} }
func deleteIdentity(id: Identity.Id) -> AnyPublisher<Never, Error> { func deleteIdentity(id: Identity.Id) -> AnyPublisher<Never, Error> {
databaseWriter.writePublisher(updates: IdentityRecord.filter(IdentityRecord.Columns.id == id).deleteAll) databaseWriter.mutatingPublisher(updates: IdentityRecord.filter(IdentityRecord.Columns.id == id).deleteAll)
.ignoreOutput()
.eraseToAnyPublisher()
} }
func updateLastUsedAt(id: Identity.Id) -> AnyPublisher<Never, Error> { func updateLastUsedAt(id: Identity.Id) -> AnyPublisher<Never, Error> {
databaseWriter.writePublisher { databaseWriter.mutatingPublisher {
try IdentityRecord try IdentityRecord
.filter(IdentityRecord.Columns.id == id) .filter(IdentityRecord.Columns.id == id)
.updateAll($0, IdentityRecord.Columns.lastUsedAt.set(to: Date())) .updateAll($0, IdentityRecord.Columns.lastUsedAt.set(to: Date()))
} }
.ignoreOutput()
.eraseToAnyPublisher()
} }
func updateInstance(_ instance: Instance, id: Identity.Id) -> AnyPublisher<Never, Error> { func updateInstance(_ instance: Instance, id: Identity.Id) -> AnyPublisher<Never, Error> {
databaseWriter.writePublisher { databaseWriter.mutatingPublisher {
try Identity.Instance( try Identity.Instance(
uri: instance.uri, uri: instance.uri,
streamingAPI: instance.urls.streamingApi, streamingAPI: instance.urls.streamingApi,
@ -78,12 +72,10 @@ public extension IdentityDatabase {
.filter(IdentityRecord.Columns.id == id) .filter(IdentityRecord.Columns.id == id)
.updateAll($0, IdentityRecord.Columns.instanceURI.set(to: instance.uri)) .updateAll($0, IdentityRecord.Columns.instanceURI.set(to: instance.uri))
} }
.ignoreOutput()
.eraseToAnyPublisher()
} }
func updateAccount(_ account: Account, id: Identity.Id) -> AnyPublisher<Never, Error> { func updateAccount(_ account: Account, id: Identity.Id) -> AnyPublisher<Never, Error> {
databaseWriter.writePublisher( databaseWriter.mutatingPublisher(
updates: Identity.Account( updates: Identity.Account(
id: account.id, id: account.id,
identityId: id, identityId: id,
@ -97,22 +89,18 @@ public extension IdentityDatabase {
emojis: account.emojis, emojis: account.emojis,
followRequestCount: account.source?.followRequestsCount ?? 0) followRequestCount: account.source?.followRequestsCount ?? 0)
.save) .save)
.ignoreOutput()
.eraseToAnyPublisher()
} }
func confirmIdentity(id: Identity.Id) -> AnyPublisher<Never, Error> { func confirmIdentity(id: Identity.Id) -> AnyPublisher<Never, Error> {
databaseWriter.writePublisher { databaseWriter.mutatingPublisher {
try IdentityRecord try IdentityRecord
.filter(IdentityRecord.Columns.id == id) .filter(IdentityRecord.Columns.id == id)
.updateAll($0, IdentityRecord.Columns.pending.set(to: false)) .updateAll($0, IdentityRecord.Columns.pending.set(to: false))
} }
.ignoreOutput()
.eraseToAnyPublisher()
} }
func updatePreferences(_ preferences: Mastodon.Preferences, id: Identity.Id) -> AnyPublisher<Never, Error> { func updatePreferences(_ preferences: Mastodon.Preferences, id: Identity.Id) -> AnyPublisher<Never, Error> {
databaseWriter.writePublisher { databaseWriter.mutatingPublisher {
guard let storedPreferences = try IdentityRecord.filter(IdentityRecord.Columns.id == id) guard let storedPreferences = try IdentityRecord.filter(IdentityRecord.Columns.id == id)
.fetchOne($0)? .fetchOne($0)?
.preferences else { .preferences else {
@ -121,20 +109,16 @@ public extension IdentityDatabase {
try Self.writePreferences(storedPreferences.updated(from: preferences), id: id)($0) try Self.writePreferences(storedPreferences.updated(from: preferences), id: id)($0)
} }
.ignoreOutput()
.eraseToAnyPublisher()
} }
func updatePreferences(_ preferences: Identity.Preferences, id: Identity.Id) -> AnyPublisher<Never, Error> { func updatePreferences(_ preferences: Identity.Preferences, id: Identity.Id) -> AnyPublisher<Never, Error> {
databaseWriter.writePublisher(updates: Self.writePreferences(preferences, id: id)) databaseWriter.mutatingPublisher(updates: Self.writePreferences(preferences, id: id))
.ignoreOutput()
.eraseToAnyPublisher()
} }
func updatePushSubscription(alerts: PushSubscription.Alerts, func updatePushSubscription(alerts: PushSubscription.Alerts,
deviceToken: Data? = nil, deviceToken: Data? = nil,
id: Identity.Id) -> AnyPublisher<Never, Error> { id: Identity.Id) -> AnyPublisher<Never, Error> {
databaseWriter.writePublisher { databaseWriter.mutatingPublisher {
let data = try IdentityRecord.databaseJSONEncoder( let data = try IdentityRecord.databaseJSONEncoder(
for: IdentityRecord.Columns.pushSubscriptionAlerts.name) for: IdentityRecord.Columns.pushSubscriptionAlerts.name)
.encode(alerts) .encode(alerts)
@ -149,8 +133,6 @@ public extension IdentityDatabase {
.updateAll($0, IdentityRecord.Columns.lastRegisteredDeviceToken.set(to: deviceToken)) .updateAll($0, IdentityRecord.Columns.lastRegisteredDeviceToken.set(to: deviceToken))
} }
} }
.ignoreOutput()
.eraseToAnyPublisher()
} }
func identityPublisher(id: Identity.Id, immediate: Bool) -> AnyPublisher<Identity, Error> { func identityPublisher(id: Identity.Id, immediate: Bool) -> AnyPublisher<Identity, Error> {

View file

@ -1,6 +1,8 @@
// Copyright © 2020 Metabolist. All rights reserved. // Copyright © 2020 Metabolist. All rights reserved.
import AVKit import AVKit
import Combine
import GRDB
import ServiceLayer import ServiceLayer
import SwiftUI import SwiftUI
import ViewModels import ViewModels
@ -8,10 +10,36 @@ import ViewModels
@main @main
struct MetatextApp: App { struct MetatextApp: App {
@UIApplicationDelegateAdaptor(AppDelegate.self) private var appDelegate @UIApplicationDelegateAdaptor(AppDelegate.self) private var appDelegate
private var cancellables = Set<AnyCancellable>()
init() { init() {
try? AVAudioSession.sharedInstance().setCategory(.ambient, mode: .default) try? AVAudioSession.sharedInstance().setCategory(.ambient, mode: .default)
try? ImageCacheConfiguration(environment: Self.environment).configure() try? ImageCacheConfiguration(environment: Self.environment).configure()
// swiftlint:disable:next line_length
// https://github.com/groue/GRDB.swift/blob/master/Documentation/SharingADatabase.md#how-to-limit-the-0xdead10cc-exception
// This would ideally be accomplished with `@Environment(\.scenePhase) private var scenePhase`
// and `.onChange(of: scenePhase)` on the `WindowGroup`, but that does not give an accurate
// aggregate scene activation state for iPad multitasking as of iOS 14.4.1
Publishers.MergeMany([UIScene.willConnectNotification,
UIScene.didDisconnectNotification,
UIScene.didActivateNotification,
UIScene.willDeactivateNotification,
UIScene.willEnterForegroundNotification,
UIScene.didEnterBackgroundNotification]
.map { NotificationCenter.default.publisher(for: $0) })
.map { _ in
UIApplication.shared.openSessions
.compactMap(\.scene)
.allSatisfy { $0.activationState == .background }
}
.removeDuplicates()
.sink {
NotificationCenter.default.post(
name: $0 ? Database.suspendNotification : Database.resumeNotification,
object: nil)
}
.store(in: &cancellables)
} }
var body: some Scene { var body: some Scene {