Compare commits

...

5 Commits

Author SHA1 Message Date
Shadowfacts a703b7cc0a Prune offscreen rows on new timeline 2022-10-09 20:11:00 -04:00
Shadowfacts e78bec8409 Fix sensitive attachments not being hidden in new timeline 2022-10-09 19:15:41 -04:00
Shadowfacts 412e4a4dc5 Fix public timeline descriptions not working
Closes #182
2022-10-09 19:11:34 -04:00
Shadowfacts 81e10326d3 Add logging to persistent store 2022-10-09 17:09:55 -04:00
Shadowfacts 20f88ef161 Fix debug logs not working
Apparently only values in Info.plist do substitution
2022-10-09 16:46:40 -04:00
9 changed files with 81 additions and 38 deletions

View File

@ -10,6 +10,9 @@ import Foundation
import CoreData import CoreData
import Pachyderm import Pachyderm
import Combine import Combine
import OSLog
fileprivate let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "PersistentStore")
class MastodonCachePersistentStore: NSPersistentContainer { class MastodonCachePersistentStore: NSPersistentContainer {
@ -49,7 +52,8 @@ class MastodonCachePersistentStore: NSPersistentContainer {
loadPersistentStores { (description, error) in loadPersistentStores { (description, error) in
if let error = error { if let error = error {
fatalError("Unable to load persistent store: \(error)") logger.error("Unable to load persistent store: \(String(describing: error), privacy: .public)")
fatalError("Unable to load persistent store")
} }
} }
@ -58,6 +62,18 @@ class MastodonCachePersistentStore: NSPersistentContainer {
NotificationCenter.default.addObserver(self, selector: #selector(managedObjectsDidChange), name: .NSManagedObjectContextObjectsDidChange, object: viewContext) NotificationCenter.default.addObserver(self, selector: #selector(managedObjectsDidChange), name: .NSManagedObjectContextObjectsDidChange, object: viewContext)
} }
func save(context: NSManagedObjectContext) {
guard context.hasChanges else {
return
}
do {
try context.save()
} catch {
logger.error("Unable to save managed object context: \(String(describing: error), privacy: .public)")
fatalError("Unable to save managed object context")
}
}
func status(for id: String, in context: NSManagedObjectContext? = nil) -> StatusMO? { func status(for id: String, in context: NSManagedObjectContext? = nil) -> StatusMO? {
let context = context ?? viewContext let context = context ?? viewContext
let request: NSFetchRequest<StatusMO> = StatusMO.fetchRequest() let request: NSFetchRequest<StatusMO> = StatusMO.fetchRequest()
@ -84,9 +100,7 @@ class MastodonCachePersistentStore: NSPersistentContainer {
let context = context ?? backgroundContext let context = context ?? backgroundContext
context.perform { context.perform {
let statusMO = self.upsert(status: status, context: context) let statusMO = self.upsert(status: status, context: context)
if context.hasChanges { self.save(context: context)
try! context.save()
}
completion?(statusMO) completion?(statusMO)
self.statusSubject.send(status.id) self.statusSubject.send(status.id)
} }
@ -95,9 +109,7 @@ class MastodonCachePersistentStore: NSPersistentContainer {
@MainActor @MainActor
func addOrUpdateOnViewContext(status: Status) -> StatusMO { func addOrUpdateOnViewContext(status: Status) -> StatusMO {
let statusMO = self.upsert(status: status, context: viewContext) let statusMO = self.upsert(status: status, context: viewContext)
if viewContext.hasChanges { self.save(context: viewContext)
try! viewContext.save()
}
statusSubject.send(status.id) statusSubject.send(status.id)
return statusMO return statusMO
} }
@ -105,9 +117,7 @@ class MastodonCachePersistentStore: NSPersistentContainer {
func addAll(statuses: [Status], completion: (() -> Void)? = nil) { func addAll(statuses: [Status], completion: (() -> Void)? = nil) {
backgroundContext.perform { backgroundContext.perform {
statuses.forEach { self.upsert(status: $0, context: self.backgroundContext) } statuses.forEach { self.upsert(status: $0, context: self.backgroundContext) }
if self.backgroundContext.hasChanges { self.save(context: self.backgroundContext)
try! self.backgroundContext.save()
}
statuses.forEach { self.statusSubject.send($0.id) } statuses.forEach { self.statusSubject.send($0.id) }
completion?() completion?()
} }
@ -146,9 +156,7 @@ class MastodonCachePersistentStore: NSPersistentContainer {
func addOrUpdate(account: Account, completion: ((AccountMO) -> Void)? = nil) { func addOrUpdate(account: Account, completion: ((AccountMO) -> Void)? = nil) {
backgroundContext.perform { backgroundContext.perform {
let accountMO = self.upsert(account: account) let accountMO = self.upsert(account: account)
if self.backgroundContext.hasChanges { self.save(context: self.backgroundContext)
try! self.backgroundContext.save()
}
completion?(accountMO) completion?(accountMO)
self.accountSubject.send(account.id) self.accountSubject.send(account.id)
} }
@ -180,9 +188,7 @@ class MastodonCachePersistentStore: NSPersistentContainer {
func addOrUpdate(relationship: Relationship, completion: ((RelationshipMO) -> Void)? = nil) { func addOrUpdate(relationship: Relationship, completion: ((RelationshipMO) -> Void)? = nil) {
backgroundContext.perform { backgroundContext.perform {
let relationshipMO = self.upsert(relationship: relationship) let relationshipMO = self.upsert(relationship: relationship)
if self.backgroundContext.hasChanges { self.save(context: self.backgroundContext)
try! self.backgroundContext.save()
}
completion?(relationshipMO) completion?(relationshipMO)
self.relationshipSubject.send(relationship.id) self.relationshipSubject.send(relationship.id)
} }
@ -191,9 +197,7 @@ class MastodonCachePersistentStore: NSPersistentContainer {
func addAll(accounts: [Account], completion: (() -> Void)? = nil) { func addAll(accounts: [Account], completion: (() -> Void)? = nil) {
backgroundContext.perform { backgroundContext.perform {
accounts.forEach { self.upsert(account: $0) } accounts.forEach { self.upsert(account: $0) }
if self.backgroundContext.hasChanges { self.save(context: self.backgroundContext)
try! self.backgroundContext.save()
}
completion?() completion?()
accounts.forEach { self.accountSubject.send($0.id) } accounts.forEach { self.accountSubject.send($0.id) }
} }
@ -207,9 +211,7 @@ class MastodonCachePersistentStore: NSPersistentContainer {
let accounts = notifications.filter { $0.kind != .mention }.map { $0.account } let accounts = notifications.filter { $0.kind != .mention }.map { $0.account }
statuses.forEach { self.upsert(status: $0, context: self.backgroundContext) } statuses.forEach { self.upsert(status: $0, context: self.backgroundContext) }
accounts.forEach { self.upsert(account: $0) } accounts.forEach { self.upsert(account: $0) }
if self.backgroundContext.hasChanges { self.save(context: self.backgroundContext)
try! self.backgroundContext.save()
}
completion?() completion?()
statuses.forEach { self.statusSubject.send($0.id) } statuses.forEach { self.statusSubject.send($0.id) }
accounts.forEach { self.accountSubject.send($0.id) } accounts.forEach { self.accountSubject.send($0.id) }
@ -232,9 +234,7 @@ class MastodonCachePersistentStore: NSPersistentContainer {
updatedAccounts.forEach(self.accountSubject.send) updatedAccounts.forEach(self.accountSubject.send)
updatedStatuses.forEach(self.statusSubject.send) updatedStatuses.forEach(self.statusSubject.send)
if self.backgroundContext.hasChanges { self.save(context: self.backgroundContext)
try! self.backgroundContext.save()
}
completion?() completion?()
} }
} }

View File

@ -4,7 +4,7 @@
<dict> <dict>
<key>OSLogPreferences</key> <key>OSLogPreferences</key>
<dict> <dict>
<key>$(PRODUCT_BUNDLE_IDENTIFIER)</key> <key>space.vaccor.Tusker</key>
<dict> <dict>
<key>DEFAULT-OPTIONS</key> <key>DEFAULT-OPTIONS</key>
<dict> <dict>

View File

@ -58,7 +58,7 @@ class HashtagTimelineViewController: TimelineTableViewController {
} else { } else {
_ = SavedHashtag(hashtag: hashtag, context: context) _ = SavedHashtag(hashtag: hashtag, context: context)
} }
try! context.save() mastodonController.persistentContainer.save(context: context)
} }
} }

View File

@ -94,7 +94,7 @@ class InstanceTimelineViewController: TimelineTableViewController {
_ = SavedInstance(url: instanceURL, context: context) _ = SavedInstance(url: instanceURL, context: context)
delegate?.didSaveInstance(url: instanceURL) delegate?.didSaveInstance(url: instanceURL)
} }
try? context.save() mastodonController.persistentContainer.save(context: context)
} }
} }

View File

@ -124,7 +124,7 @@ class TimelineViewController: UIViewController, TimelineLikeCollectionViewContro
private func applyInitialSnapshot() { private func applyInitialSnapshot() {
if case .public(let local) = timeline, if case .public(let local) = timeline,
(local && !Preferences.shared.hasShownLocalTimelineDescription) || (local && !Preferences.shared.hasShownLocalTimelineDescription) ||
(!local && Preferences.shared.hasShownFederatedTimelineDescription) { (!local && !Preferences.shared.hasShownFederatedTimelineDescription) {
var snapshot = dataSource.snapshot() var snapshot = dataSource.snapshot()
snapshot.appendSections([.header]) snapshot.appendSections([.header])
snapshot.appendItems([.publicTimelineDescription], toSection: .header) snapshot.appendItems([.publicTimelineDescription], toSection: .header)
@ -144,6 +144,25 @@ class TimelineViewController: UIViewController, TimelineLikeCollectionViewContro
} }
} }
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if isShowingTimelineDescription,
case .public(let local) = timeline {
if local {
Preferences.shared.hasShownLocalTimelineDescription = true
} else {
Preferences.shared.hasShownFederatedTimelineDescription = true
}
}
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
pruneOffscreenRows()
}
private func removeTimelineDescriptionCell() { private func removeTimelineDescriptionCell() {
var snapshot = dataSource.snapshot() var snapshot = dataSource.snapshot()
snapshot.deleteSections([.header]) snapshot.deleteSections([.header])
@ -151,6 +170,25 @@ class TimelineViewController: UIViewController, TimelineLikeCollectionViewContro
isShowingTimelineDescription = false isShowingTimelineDescription = false
} }
private func pruneOffscreenRows() {
guard let lastVisibleIndexPath = collectionView.indexPathsForVisibleItems.last else {
return
}
var snapshot = dataSource.snapshot()
let items = snapshot.itemIdentifiers(inSection: .statuses)
let pageSize = 20
let numberOfPagesToPrune = (items.count - lastVisibleIndexPath.row - 1) / pageSize
if numberOfPagesToPrune > 0 {
let itemsToRemove = Array(items.suffix(numberOfPagesToPrune * pageSize))
snapshot.deleteItems(itemsToRemove)
}
dataSource.apply(snapshot, animatingDifferences: false)
if case .status(id: let id, state: _) = snapshot.itemIdentifiers(inSection: .statuses).last {
older = .before(id: id, count: nil)
}
}
@objc func refresh() { @objc func refresh() {
Task { Task {
await controller.loadNewer() await controller.loadNewer()

View File

@ -110,7 +110,7 @@ extension MenuActionProvider {
} else { } else {
_ = SavedHashtag(hashtag: hashtag, context: context) _ = SavedHashtag(hashtag: hashtag, context: context)
} }
try! context.save() mastodonController.persistentContainer.save(context: context)
}) })
] ]
} else { } else {

View File

@ -33,9 +33,17 @@ class AttachmentsContainerView: UIView {
} }
} }
override func awakeFromNib() { override init(frame: CGRect) {
super.awakeFromNib() super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
private func commonInit() {
self.isUserInteractionEnabled = true self.isUserInteractionEnabled = true
self.layer.cornerRadius = 5 self.layer.cornerRadius = 5
self.layer.masksToBounds = true self.layer.masksToBounds = true

View File

@ -170,9 +170,7 @@ class StatusPollView: UIView {
return return
} }
status.poll = poll status.poll = poll
if container.viewContext.hasChanges { container.save(context: container.viewContext)
try! container.viewContext.save()
}
container.statusSubject.send(status.id) container.statusSubject.send(status.id)
} }
} }

View File

@ -85,13 +85,12 @@ extension StatusCollectionViewCell {
contentContainer.contentTextView.setTextFrom(status: status) contentContainer.contentTextView.setTextFrom(status: status)
contentContainer.attachmentsView.delegate = self contentContainer.attachmentsView.delegate = self
contentContainer.attachmentsView.updateUI(status: status)
contentContainer.cardView.updateUI(status: status) contentContainer.cardView.updateUI(status: status)
contentContainer.cardView.isHidden = status.card == nil contentContainer.cardView.isHidden = status.card == nil
contentContainer.cardView.navigationDelegate = delegate contentContainer.cardView.navigationDelegate = delegate
contentContainer.cardView.actionProvider = delegate contentContainer.cardView.actionProvider = delegate
contentContainer.attachmentsView.updateUI(status: status)
updateStatusState(status: status) updateStatusState(status: status)
contentWarningLabel.text = status.spoilerText contentWarningLabel.text = status.spoilerText