This commit fixes the background crashes with termination code 0xdead10cc. Those crashes were caused by the fact that NostrDB was being stored on the shared app container (Because our app extensions need NostrDB data), and iOS kills any process that holds a file lock after the process is backgrounded. Other developers in the field have run into similar problems in the past (with shared SQLite databases or shared SwiftData), and they generally recommend not to place those database in shared containers at all, mentioning that 0xdead10cc crashes are almost inevitable otherwise: - https://ryanashcraft.com/sqlite-databases-in-app-group-containers/ - https://inessential.com/2020/02/13/how_we_fixed_the_dreaded_0xdead10cc_cras.html Since iOS aggressively backgrounds and terminates processes with tight timing constraints that are mostly outside our control (despite using Apple's recommended mechanisms, such as requesting more time to perform closing operations), this fix aims to address the issue by a different storage architecture. Instead of keeping NostrDB data on the shared app container and handling the closure/opening of the database with the app lifecycle signals, keep the main NostrDB database file in the app's private container, and instead take periodic read-only snapshots of NostrDB in the shared container, so as to allow extensions to have recent NostrDB data without all the complexities of keeping the main file in the shared container. This does have the tradeoff that more storage will be used by NostrDB due to file duplication, but that can be mitigated via other techniques if necessary. Closes: https://github.com/damus-io/damus/issues/2638 Closes: https://github.com/damus-io/damus/issues/3463 Changelog-Fixed: Fixed background crashes with error code 0xdead10cc Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
287 lines
12 KiB
Swift
287 lines
12 KiB
Swift
//
|
|
// Router.swift
|
|
// damus
|
|
//
|
|
// Created by Scott Penrose on 5/7/23.
|
|
//
|
|
|
|
import FaviconFinder
|
|
import SwiftUI
|
|
|
|
enum Route: Hashable {
|
|
case ProfileByKey(pubkey: Pubkey)
|
|
case Profile(profile: ProfileModel, followers: FollowersModel)
|
|
case Followers(followers: FollowersModel)
|
|
case Relay(relay: RelayURL, showActionButtons: Binding<Bool>)
|
|
case RelayDetail(relay: RelayURL, metadata: RelayMetadata?)
|
|
case Following(following: FollowingModel)
|
|
case MuteList
|
|
case RelayConfig
|
|
case Script(script: ScriptModel)
|
|
case Bookmarks
|
|
case Config
|
|
case EditMetadata
|
|
case DMChat(dms: DirectMessageModel)
|
|
case UserRelays(relays: [RelayURL])
|
|
case KeySettings(keypair: Keypair)
|
|
case AppearanceSettings(settings: UserSettingsStore)
|
|
case NotificationSettings(settings: UserSettingsStore)
|
|
case ZapSettings(settings: UserSettingsStore)
|
|
case TranslationSettings(settings: UserSettingsStore)
|
|
case ReactionsSettings(settings: UserSettingsStore)
|
|
case SearchSettings(settings: UserSettingsStore)
|
|
case DeveloperSettings(settings: UserSettingsStore)
|
|
case FirstAidSettings(settings: UserSettingsStore)
|
|
case Thread(thread: ThreadModel)
|
|
case LoadableNostrEvent(note_reference: LoadableNostrEventViewModel.NoteReference)
|
|
case Reposts(reposts: EventsModel)
|
|
case QuoteReposts(quotes: EventsModel)
|
|
case Reactions(reactions: EventsModel)
|
|
case Zaps(target: ZapTarget)
|
|
case Search(search: SearchModel)
|
|
case NDBSearch(results: Binding<[NostrEvent]>, query: String)
|
|
case EULA
|
|
case Login
|
|
case CreateAccount
|
|
case SaveKeys(account: CreateAccountModel)
|
|
case Wallet(wallet: WalletModel)
|
|
case WalletScanner(result: Binding<WalletScanResult>)
|
|
case FollowersYouKnow(friendedFollowers: [Pubkey], followers: FollowersModel)
|
|
case NIP05DomainEvents(events: NIP05DomainEventsModel, nip05_domain_favicon: FaviconURL?)
|
|
case NIP05DomainPubkeys(domain: String, nip05_domain_favicon: FaviconURL?, pubkeys: [Pubkey])
|
|
case FollowPack(followPack: NostrEvent, model: FollowPackModel, blur_imgs: Bool)
|
|
case LiveEvents(model: LiveEventModel)
|
|
case LiveEvent(LiveEvent: NostrEvent, model: LiveEventModel)
|
|
|
|
@ViewBuilder
|
|
func view(navigationCoordinator: NavigationCoordinator, damusState: DamusState) -> some View {
|
|
switch self {
|
|
case .ProfileByKey(let pubkey):
|
|
ProfileView(damus_state: damusState, pubkey: pubkey)
|
|
case .Profile(let profile, let followers):
|
|
ProfileView(damus_state: damusState, profile: profile, followers: followers)
|
|
case .Followers(let followers):
|
|
FollowersView(damus_state: damusState, followers: followers)
|
|
case .Relay(let relay, let showActionButtons):
|
|
RelayView(state: damusState, relay: relay, showActionButtons: showActionButtons, recommended: false)
|
|
case .RelayDetail(let relay, let metadata):
|
|
RelayDetailView(state: damusState, relay: relay, nip11: metadata)
|
|
case .Following(let following):
|
|
FollowingView(damus_state: damusState, following: following)
|
|
case .MuteList:
|
|
MutelistView(damus_state: damusState)
|
|
case .RelayConfig:
|
|
RelayConfigView(state: damusState)
|
|
case .Bookmarks:
|
|
BookmarksView(state: damusState)
|
|
case .Config:
|
|
ConfigView(state: damusState)
|
|
case .EditMetadata:
|
|
EditMetadataView(damus_state: damusState)
|
|
case .DMChat(let dms):
|
|
DMChatView(damus_state: damusState, dms: dms)
|
|
case .UserRelays(let relays):
|
|
UserRelaysView(state: damusState, relays: relays)
|
|
case .KeySettings(let keypair):
|
|
KeySettingsView(keypair: keypair)
|
|
case .AppearanceSettings(let settings):
|
|
AppearanceSettingsView(damus_state: damusState, settings: settings)
|
|
case .NotificationSettings(let settings):
|
|
NotificationSettingsView(damus_state: damusState, settings: settings)
|
|
case .ZapSettings(let settings):
|
|
ZapSettingsView(settings: settings)
|
|
case .TranslationSettings(let settings):
|
|
TranslationSettingsView(settings: settings, damus_state: damusState)
|
|
case .ReactionsSettings(let settings):
|
|
ReactionsSettingsView(settings: settings, damus_state: damusState)
|
|
case .SearchSettings(let settings):
|
|
SearchSettingsView(settings: settings)
|
|
case .DeveloperSettings(let settings):
|
|
DeveloperSettingsView(settings: settings, damus_state: damusState)
|
|
case .FirstAidSettings(settings: let settings):
|
|
FirstAidSettingsView(damus_state: damusState, settings: settings)
|
|
case .Thread(let thread):
|
|
ChatroomThreadView(damus: damusState, thread: thread)
|
|
//ThreadView(state: damusState, thread: thread)
|
|
case .LoadableNostrEvent(let note_reference):
|
|
LoadableNostrEventView(state: damusState, note_reference: note_reference)
|
|
case .Reposts(let reposts):
|
|
RepostsView(damus_state: damusState, model: reposts)
|
|
case .QuoteReposts(let quote_reposts):
|
|
QuoteRepostsView(damus_state: damusState, model: quote_reposts)
|
|
case .Reactions(let reactions):
|
|
ReactionsView(damus_state: damusState, model: reactions)
|
|
case .Zaps(let target):
|
|
ZapsView(state: damusState, target: target)
|
|
case .Search(let search):
|
|
SearchView(appstate: damusState, search: search)
|
|
case .NDBSearch(let results, let query):
|
|
NDBSearchView(damus_state: damusState, results: results, searchQuery: query)
|
|
case .EULA:
|
|
EULAView(nav: navigationCoordinator)
|
|
case .Login:
|
|
LoginView(nav: navigationCoordinator)
|
|
case .CreateAccount:
|
|
CreateAccountView(nav: navigationCoordinator)
|
|
case .SaveKeys(let account):
|
|
SaveKeysView(account: account)
|
|
case .Wallet(let walletModel):
|
|
WalletView(damus_state: damusState, model: walletModel)
|
|
case .WalletScanner(let walletScanResult):
|
|
WalletScannerView(result: walletScanResult)
|
|
case .FollowersYouKnow(let friendedFollowers, let followers):
|
|
FollowersYouKnowView(damus_state: damusState, friended_followers: friendedFollowers, followers: followers)
|
|
case .Script(let load_model):
|
|
LoadScript(pool: RelayPool(ndb: damusState.ndb, keypair: damusState.keypair), model: load_model)
|
|
case .NIP05DomainEvents(let events, let nip05_domain_favicon):
|
|
NIP05DomainTimelineView(damus_state: damusState, model: events, nip05_domain_favicon: nip05_domain_favicon)
|
|
case .NIP05DomainPubkeys(let domain, let nip05_domain_favicon, let pubkeys):
|
|
NIP05DomainPubkeysView(damus_state: damusState, domain: domain, nip05_domain_favicon: nip05_domain_favicon, pubkeys: pubkeys)
|
|
case .FollowPack(let followPack, let followPackModel, let blur_imgs):
|
|
FollowPackView(state: damusState, ev: followPack, model: followPackModel, blur_imgs: blur_imgs)
|
|
case .LiveEvents(let model):
|
|
LiveStreamHomeView(damus_state: damusState, model: model)
|
|
case .LiveEvent(let liveEvent, let liveEventModel):
|
|
LiveStreamView(state: damusState, ev: liveEvent, model: liveEventModel)
|
|
}
|
|
|
|
}
|
|
|
|
static func == (lhs: Route, rhs: Route) -> Bool {
|
|
return lhs.hashValue == rhs.hashValue
|
|
}
|
|
|
|
func hash(into hasher: inout Hasher) {
|
|
switch self {
|
|
case .ProfileByKey(let pubkey):
|
|
hasher.combine("profilebykey")
|
|
hasher.combine(pubkey.id.bytes)
|
|
case .Profile(let profile, _):
|
|
hasher.combine("profile")
|
|
hasher.combine(profile.pubkey.id.bytes)
|
|
case .Followers:
|
|
hasher.combine("followers")
|
|
case .Relay(let relay, _):
|
|
hasher.combine("relay")
|
|
hasher.combine(relay)
|
|
case .RelayDetail(let relay, _):
|
|
hasher.combine("relayDetail")
|
|
hasher.combine(relay)
|
|
case .Following:
|
|
hasher.combine("following")
|
|
case .MuteList:
|
|
hasher.combine("muteList")
|
|
case .RelayConfig:
|
|
hasher.combine("relayConfig")
|
|
case .Bookmarks:
|
|
hasher.combine("bookmarks")
|
|
case .Config:
|
|
hasher.combine("config")
|
|
case .EditMetadata:
|
|
hasher.combine("editMetadata")
|
|
case .DMChat(let dms):
|
|
hasher.combine("dms")
|
|
hasher.combine(dms.our_pubkey)
|
|
case .UserRelays(let relays):
|
|
hasher.combine("userRelays")
|
|
hasher.combine(relays)
|
|
case .KeySettings(let keypair):
|
|
hasher.combine("keySettings")
|
|
hasher.combine(keypair.pubkey)
|
|
case .AppearanceSettings:
|
|
hasher.combine("appearanceSettings")
|
|
case .NotificationSettings:
|
|
hasher.combine("notificationSettings")
|
|
case .ZapSettings:
|
|
hasher.combine("zapSettings")
|
|
case .TranslationSettings:
|
|
hasher.combine("translationSettings")
|
|
case .ReactionsSettings:
|
|
hasher.combine("reactionsSettings")
|
|
case .SearchSettings:
|
|
hasher.combine("searchSettings")
|
|
case .DeveloperSettings:
|
|
hasher.combine("developerSettings")
|
|
case .FirstAidSettings:
|
|
hasher.combine("firstAidSettings")
|
|
case .Thread(let threadModel):
|
|
hasher.combine("thread")
|
|
hasher.combine(threadModel.original_event.id)
|
|
case .LoadableNostrEvent(note_reference: let note_reference):
|
|
hasher.combine("loadable_nostr_event")
|
|
hasher.combine(note_reference)
|
|
case .Reposts(let reposts):
|
|
hasher.combine("reposts")
|
|
hasher.combine(reposts.target)
|
|
case .QuoteReposts(let evs_model):
|
|
hasher.combine("quote_reposts")
|
|
hasher.combine(evs_model.events.events.count)
|
|
case .Zaps(let target):
|
|
hasher.combine("zaps")
|
|
hasher.combine(target.id)
|
|
hasher.combine(target.pubkey)
|
|
case .Reactions(let reactions):
|
|
hasher.combine("reactions")
|
|
hasher.combine(reactions.target)
|
|
case .Search(let search):
|
|
hasher.combine("search")
|
|
hasher.combine(search.search)
|
|
case .NDBSearch(_, let query):
|
|
hasher.combine("results")
|
|
hasher.combine(query)
|
|
case .EULA:
|
|
hasher.combine("eula")
|
|
case .Login:
|
|
hasher.combine("login")
|
|
case .CreateAccount:
|
|
hasher.combine("createAccount")
|
|
case .SaveKeys(let account):
|
|
hasher.combine("saveKeys")
|
|
hasher.combine(account.pubkey)
|
|
case .Wallet:
|
|
hasher.combine("wallet")
|
|
case .WalletScanner:
|
|
hasher.combine("walletScanner")
|
|
case .FollowersYouKnow(let friendedFollowers, let followers):
|
|
hasher.combine("followersYouKnow")
|
|
hasher.combine(friendedFollowers)
|
|
case .Script(let model):
|
|
hasher.combine("script")
|
|
hasher.combine(model.data.count)
|
|
case .NIP05DomainEvents(let events, _):
|
|
hasher.combine("nip05DomainEvents")
|
|
hasher.combine(events.domain)
|
|
case .NIP05DomainPubkeys(let domain, _, _):
|
|
hasher.combine("nip05DomainPubkeys")
|
|
hasher.combine(domain)
|
|
case .FollowPack(let followPack, let followPackModel, let blur_imgs):
|
|
hasher.combine("followPack")
|
|
hasher.combine(followPack.id)
|
|
case .LiveEvents(let model):
|
|
hasher.combine("liveEvents")
|
|
case .LiveEvent(let liveEvent, let liveEventModel):
|
|
hasher.combine("liveEvent")
|
|
hasher.combine(liveEvent.id)
|
|
}
|
|
}
|
|
}
|
|
|
|
class NavigationCoordinator: ObservableObject {
|
|
@Published var path = [Route]()
|
|
|
|
func push(route: Route) {
|
|
guard route != path.last else {
|
|
return
|
|
}
|
|
path.append(route)
|
|
}
|
|
|
|
func isAtRoot() -> Bool {
|
|
return path.count == 0
|
|
}
|
|
|
|
func popToRoot() {
|
|
path = []
|
|
}
|
|
}
|