From d04957ba41c9d04a6e296663bbe89068b0b93515 Mon Sep 17 00:00:00 2001 From: Shadowfacts Date: Sun, 1 May 2022 15:15:35 -0400 Subject: [PATCH] Remove reference counting system Delete statuses/accounts that haven't been fetched in a week --- .../BookmarkStatusActivity.swift | 2 +- .../MuteConversationActivity.swift | 2 +- .../Status Activities/PinStatusActivity.swift | 2 +- .../UnbookmarkStatusActivity.swift | 2 +- .../UnmuteConversationActivity.swift | 2 +- .../UnpinStatusActivity.swift | 2 +- Tusker/Controllers/MastodonController.swift | 2 +- Tusker/CoreData/AccountMO.swift | 22 +++------- .../MastodonCachePersistentStore.swift | 42 +++++++------------ Tusker/CoreData/StatusMO.swift | 24 +++-------- .../Tusker.xcdatamodel/contents | 8 ++-- Tusker/MainSceneDelegate.swift | 18 +++++++- .../BookmarksTableViewController.swift | 2 +- .../ConversationTableViewController.swift | 12 +----- .../Profile/ProfileViewController.swift | 8 +--- .../Search/SearchResultsViewController.swift | 15 ------- ...ActionAccountListTableViewController.swift | 16 +------ .../TimelineTableViewController.swift | 18 -------- Tusker/Screens/Utilities/Previewing.swift | 10 ++--- .../Status/BaseStatusTableViewCell.swift | 4 +- .../Status/TimelineStatusTableViewCell.swift | 4 +- 21 files changed, 67 insertions(+), 150 deletions(-) diff --git a/Tusker/Activities/Status Activities/BookmarkStatusActivity.swift b/Tusker/Activities/Status Activities/BookmarkStatusActivity.swift index b6252667f8..1d16043a4a 100644 --- a/Tusker/Activities/Status Activities/BookmarkStatusActivity.swift +++ b/Tusker/Activities/Status Activities/BookmarkStatusActivity.swift @@ -29,7 +29,7 @@ class BookmarkStatusActivity: StatusActivity { let request = Status.bookmark(status.id) mastodonController.run(request) { (response) in if case let .success(status, _) = response { - self.mastodonController.persistentContainer.addOrUpdate(status: status, incrementReferenceCount: false) + self.mastodonController.persistentContainer.addOrUpdate(status: status) } else { // todo: display error message UINotificationFeedbackGenerator().notificationOccurred(.error) diff --git a/Tusker/Activities/Status Activities/MuteConversationActivity.swift b/Tusker/Activities/Status Activities/MuteConversationActivity.swift index 1c622eca75..f9eecdfa6d 100644 --- a/Tusker/Activities/Status Activities/MuteConversationActivity.swift +++ b/Tusker/Activities/Status Activities/MuteConversationActivity.swift @@ -28,7 +28,7 @@ class MuteConversationActivity: StatusActivity { let request = Status.muteConversation(status.id) mastodonController.run(request) { (response) in if case let .success(status, _) = response { - self.mastodonController.persistentContainer.addOrUpdate(status: status, incrementReferenceCount: false) + self.mastodonController.persistentContainer.addOrUpdate(status: status) } else { // todo: display error message UINotificationFeedbackGenerator().notificationOccurred(.error) diff --git a/Tusker/Activities/Status Activities/PinStatusActivity.swift b/Tusker/Activities/Status Activities/PinStatusActivity.swift index 3d9e5e53d3..af939b3073 100644 --- a/Tusker/Activities/Status Activities/PinStatusActivity.swift +++ b/Tusker/Activities/Status Activities/PinStatusActivity.swift @@ -28,7 +28,7 @@ class PinStatusActivity: StatusActivity { let request = Status.pin(status.id) mastodonController.run(request) { (response) in if case let .success(status, _) = response { - self.mastodonController.persistentContainer.addOrUpdate(status: status, incrementReferenceCount: false) + self.mastodonController.persistentContainer.addOrUpdate(status: status) } else { // todo: display error message UINotificationFeedbackGenerator().notificationOccurred(.error) diff --git a/Tusker/Activities/Status Activities/UnbookmarkStatusActivity.swift b/Tusker/Activities/Status Activities/UnbookmarkStatusActivity.swift index 953c8ce389..661aaab2a6 100644 --- a/Tusker/Activities/Status Activities/UnbookmarkStatusActivity.swift +++ b/Tusker/Activities/Status Activities/UnbookmarkStatusActivity.swift @@ -29,7 +29,7 @@ class UnbookmarkStatusActivity: StatusActivity { let request = Status.unbookmark(status.id) mastodonController.run(request) { (response) in if case let .success(status, _) = response { - self.mastodonController.persistentContainer.addOrUpdate(status: status, incrementReferenceCount: false) + self.mastodonController.persistentContainer.addOrUpdate(status: status) } else { // todo: display error message UINotificationFeedbackGenerator().notificationOccurred(.error) diff --git a/Tusker/Activities/Status Activities/UnmuteConversationActivity.swift b/Tusker/Activities/Status Activities/UnmuteConversationActivity.swift index 5a69664a5e..7b417f00de 100644 --- a/Tusker/Activities/Status Activities/UnmuteConversationActivity.swift +++ b/Tusker/Activities/Status Activities/UnmuteConversationActivity.swift @@ -28,7 +28,7 @@ class UnmuteConversationActivity: StatusActivity { let request = Status.unmuteConversation(status.id) mastodonController.run(request) { (response) in if case let .success(status, _) = response { - self.mastodonController.persistentContainer.addOrUpdate(status: status, incrementReferenceCount: false) + self.mastodonController.persistentContainer.addOrUpdate(status: status) } else { // todo: display error message UINotificationFeedbackGenerator().notificationOccurred(.error) diff --git a/Tusker/Activities/Status Activities/UnpinStatusActivity.swift b/Tusker/Activities/Status Activities/UnpinStatusActivity.swift index 8790ae5645..fb3bf24b45 100644 --- a/Tusker/Activities/Status Activities/UnpinStatusActivity.swift +++ b/Tusker/Activities/Status Activities/UnpinStatusActivity.swift @@ -28,7 +28,7 @@ class UnpinStatusActivity: StatusActivity { let request = Status.unpin(status.id) mastodonController.run(request) { (response) in if case let .success(status, _) = response { - self.mastodonController.persistentContainer.addOrUpdate(status: status, incrementReferenceCount: false) + self.mastodonController.persistentContainer.addOrUpdate(status: status) } else { // todo: display error message UINotificationFeedbackGenerator().notificationOccurred(.error) diff --git a/Tusker/Controllers/MastodonController.swift b/Tusker/Controllers/MastodonController.swift index a9263e9062..ddc60e00ae 100644 --- a/Tusker/Controllers/MastodonController.swift +++ b/Tusker/Controllers/MastodonController.swift @@ -137,7 +137,7 @@ class MastodonController: ObservableObject { } else { // the first time the user's account is added to the store, // increment its reference count so that it's never removed - self.persistentContainer.addOrUpdate(account: account, incrementReferenceCount: true) + self.persistentContainer.addOrUpdate(account: account) } completion?(.success(account)) } diff --git a/Tusker/CoreData/AccountMO.swift b/Tusker/CoreData/AccountMO.swift index 2ebe40e16f..41ea579037 100644 --- a/Tusker/CoreData/AccountMO.swift +++ b/Tusker/CoreData/AccountMO.swift @@ -32,10 +32,10 @@ public final class AccountMO: NSManagedObject, AccountProtocol { @NSManaged public var locked: Bool @NSManaged public var movedCD: Bool @NSManaged public var note: String - @NSManaged public var referenceCount: Int @NSManaged public var statusesCount: Int @NSManaged public var url: URL @NSManaged public var username: String + @NSManaged public var lastFetchedAt: Date? @NSManaged public var movedTo: AccountMO? @LazilyDecoding(arrayFrom: \AccountMO.emojisData) @@ -47,21 +47,13 @@ public final class AccountMO: NSManagedObject, AccountProtocol { public var bot: Bool? { botCD } public var moved: Bool? { movedCD } - func incrementReferenceCount() { - referenceCount += 1 - } - - func decrementReferenceCount() { - referenceCount -= 1 - if referenceCount <= 0 { - managedObjectContext!.delete(self) + public override func awakeFromFetch() { + super.awakeFromFetch() + + managedObjectContext?.perform { + self.lastFetchedAt = Date() } } - - public override func prepareForDeletion() { - super.prepareForDeletion() - movedTo?.decrementReferenceCount() - } } @@ -69,8 +61,6 @@ extension AccountMO { convenience init(apiAccount account: Pachyderm.Account, container: MastodonCachePersistentStore, context: NSManagedObjectContext) { self.init(context: context) self.updateFrom(apiAccount: account, container: container) - - movedTo?.incrementReferenceCount() } func updateFrom(apiAccount account: Pachyderm.Account, container: MastodonCachePersistentStore) { diff --git a/Tusker/CoreData/MastodonCachePersistentStore.swift b/Tusker/CoreData/MastodonCachePersistentStore.swift index 3ff5237baf..ebdd12faad 100644 --- a/Tusker/CoreData/MastodonCachePersistentStore.swift +++ b/Tusker/CoreData/MastodonCachePersistentStore.swift @@ -65,26 +65,19 @@ class MastodonCachePersistentStore: NSPersistentContainer { } @discardableResult - private func upsert(status: Status, incrementReferenceCount: Bool, context: NSManagedObjectContext) -> StatusMO { + private func upsert(status: Status, context: NSManagedObjectContext) -> StatusMO { if let statusMO = self.status(for: status.id, in: context) { statusMO.updateFrom(apiStatus: status, container: self) - if incrementReferenceCount { - statusMO.incrementReferenceCount() - } return statusMO } else { - let statusMO = StatusMO(apiStatus: status, container: self, context: context) - if incrementReferenceCount { - statusMO.incrementReferenceCount() - } - return statusMO + return StatusMO(apiStatus: status, container: self, context: context) } } - func addOrUpdate(status: Status, incrementReferenceCount: Bool, context: NSManagedObjectContext? = nil, completion: ((StatusMO) -> Void)? = nil) { + func addOrUpdate(status: Status, context: NSManagedObjectContext? = nil, completion: ((StatusMO) -> Void)? = nil) { let context = context ?? backgroundContext context.perform { - let statusMO = self.upsert(status: status, incrementReferenceCount: incrementReferenceCount, context: context) + let statusMO = self.upsert(status: status, context: context) if context.hasChanges { try! context.save() } @@ -95,7 +88,7 @@ class MastodonCachePersistentStore: NSPersistentContainer { func addAll(statuses: [Status], completion: (() -> Void)? = nil) { backgroundContext.perform { - statuses.forEach { self.upsert(status: $0, incrementReferenceCount: true, context: self.backgroundContext) } + statuses.forEach { self.upsert(status: $0, context: self.backgroundContext) } if self.backgroundContext.hasChanges { try! self.backgroundContext.save() } @@ -117,25 +110,18 @@ class MastodonCachePersistentStore: NSPersistentContainer { } @discardableResult - private func upsert(account: Account, incrementReferenceCount: Bool) -> AccountMO { + private func upsert(account: Account) -> AccountMO { if let accountMO = self.account(for: account.id, in: self.backgroundContext) { accountMO.updateFrom(apiAccount: account, container: self) - if incrementReferenceCount { - accountMO.incrementReferenceCount() - } return accountMO } else { - let accountMO = AccountMO(apiAccount: account, container: self, context: self.backgroundContext) - if incrementReferenceCount { - accountMO.incrementReferenceCount() - } - return accountMO + return AccountMO(apiAccount: account, container: self, context: self.backgroundContext) } } - func addOrUpdate(account: Account, incrementReferenceCount: Bool, completion: ((AccountMO) -> Void)? = nil) { + func addOrUpdate(account: Account, completion: ((AccountMO) -> Void)? = nil) { backgroundContext.perform { - let accountMO = self.upsert(account: account, incrementReferenceCount: incrementReferenceCount) + let accountMO = self.upsert(account: account) if self.backgroundContext.hasChanges { try! self.backgroundContext.save() } @@ -180,7 +166,7 @@ class MastodonCachePersistentStore: NSPersistentContainer { func addAll(accounts: [Account], completion: (() -> Void)? = nil) { backgroundContext.perform { - accounts.forEach { self.upsert(account: $0, incrementReferenceCount: true) } + accounts.forEach { self.upsert(account: $0) } if self.backgroundContext.hasChanges { try! self.backgroundContext.save() } @@ -195,8 +181,8 @@ class MastodonCachePersistentStore: NSPersistentContainer { // filter out mentions, otherwise we would double increment the reference count of those accounts // since the status has the same account as the notification let accounts = notifications.filter { $0.kind != .mention }.map { $0.account } - statuses.forEach { self.upsert(status: $0, incrementReferenceCount: true, context: self.backgroundContext) } - accounts.forEach { self.upsert(account: $0, incrementReferenceCount: true) } + statuses.forEach { self.upsert(status: $0, context: self.backgroundContext) } + accounts.forEach { self.upsert(account: $0) } if self.backgroundContext.hasChanges { try! self.backgroundContext.save() } @@ -212,10 +198,10 @@ class MastodonCachePersistentStore: NSPersistentContainer { var updatedStatuses = [String]() block(self.backgroundContext, { (accounts) in - accounts.forEach { self.upsert(account: $0, incrementReferenceCount: true) } + accounts.forEach { self.upsert(account: $0) } updatedAccounts.append(contentsOf: accounts.map { $0.id }) }, { (statuses) in - statuses.forEach { self.upsert(status: $0, incrementReferenceCount: true, context: self.backgroundContext) } + statuses.forEach { self.upsert(status: $0, context: self.backgroundContext) } updatedStatuses.append(contentsOf: statuses.map { $0.id }) }) diff --git a/Tusker/CoreData/StatusMO.swift b/Tusker/CoreData/StatusMO.swift index bb316fa540..19167e807e 100644 --- a/Tusker/CoreData/StatusMO.swift +++ b/Tusker/CoreData/StatusMO.swift @@ -36,7 +36,6 @@ public final class StatusMO: NSManagedObject, StatusProtocol { @NSManaged private var pinnedInternal: Bool @NSManaged public var reblogged: Bool @NSManaged public var reblogsCount: Int - @NSManaged public var referenceCount: Int @NSManaged public var sensitive: Bool @NSManaged public var spoilerText: String @NSManaged public var uri: String // todo: are both uri and url necessary? @@ -46,6 +45,7 @@ public final class StatusMO: NSManagedObject, StatusProtocol { @NSManaged public var account: AccountMO @NSManaged public var reblog: StatusMO? @NSManaged public var localOnly: Bool + @NSManaged public var lastFetchedAt: Date? @LazilyDecoding(arrayFrom: \StatusMO.attachmentsData) public var attachments: [Attachment] @@ -77,32 +77,20 @@ public final class StatusMO: NSManagedObject, StatusProtocol { } } - func incrementReferenceCount() { - referenceCount += 1 - } - - func decrementReferenceCount() { - referenceCount -= 1 - if referenceCount <= 0 { - managedObjectContext!.delete(self) + public override func awakeFromFetch() { + super.awakeFromFetch() + + managedObjectContext?.perform { + self.lastFetchedAt = Date() } } - public override func prepareForDeletion() { - super.prepareForDeletion() - reblog?.decrementReferenceCount() - account.decrementReferenceCount() - } - } extension StatusMO { convenience init(apiStatus status: Pachyderm.Status, container: MastodonCachePersistentStore, context: NSManagedObjectContext) { self.init(context: context) self.updateFrom(apiStatus: status, container: container) - - reblog?.incrementReferenceCount() - account.incrementReferenceCount() } func updateFrom(apiStatus status: Pachyderm.Status, container: MastodonCachePersistentStore) { diff --git a/Tusker/CoreData/Tusker.xcdatamodeld/Tusker.xcdatamodel/contents b/Tusker/CoreData/Tusker.xcdatamodeld/Tusker.xcdatamodel/contents index 0ad0d0b162..b4d726c28b 100644 --- a/Tusker/CoreData/Tusker.xcdatamodeld/Tusker.xcdatamodel/contents +++ b/Tusker/CoreData/Tusker.xcdatamodeld/Tusker.xcdatamodel/contents @@ -1,5 +1,5 @@ - + @@ -12,10 +12,10 @@ + - @@ -54,6 +54,7 @@ + @@ -61,7 +62,6 @@ - @@ -76,7 +76,7 @@ - + diff --git a/Tusker/MainSceneDelegate.swift b/Tusker/MainSceneDelegate.swift index ebb17c9a18..f9b950c08c 100644 --- a/Tusker/MainSceneDelegate.swift +++ b/Tusker/MainSceneDelegate.swift @@ -10,6 +10,7 @@ import UIKit import Pachyderm import CrashReporter import MessageUI +import CoreData class MainSceneDelegate: UIResponder, UIWindowSceneDelegate { @@ -120,7 +121,22 @@ class MainSceneDelegate: UIResponder, UIWindowSceneDelegate { rootVC.sceneDidEnterBackground() } - try? scene.session.mastodonController?.persistentContainer.viewContext.save() + if let context = scene.session.mastodonController?.persistentContainer.viewContext { + var minDate = Date() + minDate.addTimeInterval(-7 * 24 * 60 * 60) + + let statusReq: NSFetchRequest = StatusMO.fetchRequest() + statusReq.predicate = NSPredicate(format: "(lastFetchedAt = nil) OR (lastFetchedAt < %@)", minDate as NSDate) + let deleteStatusReq = NSBatchDeleteRequest(fetchRequest: statusReq) + _ = try? context.execute(deleteStatusReq) + + let accountReq: NSFetchRequest = AccountMO.fetchRequest() + accountReq.predicate = NSPredicate(format: "(lastFetchedAt = nil) OR (lastFetchedAt < %@)", minDate as NSDate) + let deleteAccountReq = NSBatchDeleteRequest(fetchRequest: accountReq) + _ = try? context.execute(deleteAccountReq) + + try? context.save() + } } private func handlePendingCrashReport(_ report: PLCrashReport, session: UISceneSession) { diff --git a/Tusker/Screens/Bookmarks/BookmarksTableViewController.swift b/Tusker/Screens/Bookmarks/BookmarksTableViewController.swift index 5169f50125..c57e2da547 100644 --- a/Tusker/Screens/Bookmarks/BookmarksTableViewController.swift +++ b/Tusker/Screens/Bookmarks/BookmarksTableViewController.swift @@ -134,7 +134,7 @@ class BookmarksTableViewController: EnhancedTableViewController { let request = Status.unbookmark(status.id) self.mastodonController.run(request) { (response) in guard case let .success(newStatus, _) = response else { fatalError() } - self.mastodonController.persistentContainer.addOrUpdate(status: newStatus, incrementReferenceCount: false) + self.mastodonController.persistentContainer.addOrUpdate(status: newStatus) self.statuses.remove(at: indexPath.row) } } diff --git a/Tusker/Screens/Conversation/ConversationTableViewController.swift b/Tusker/Screens/Conversation/ConversationTableViewController.swift index 52b400928b..137b1d47ca 100644 --- a/Tusker/Screens/Conversation/ConversationTableViewController.swift +++ b/Tusker/Screens/Conversation/ConversationTableViewController.swift @@ -52,14 +52,6 @@ class ConversationTableViewController: EnhancedTableViewController { fatalError("init(coder:) has not been implemented") } - deinit { - guard let persistentContainer = mastodonController?.persistentContainer else { return } - let snapshot = dataSource.snapshot() - for case let .status(id: id, state: _) in snapshot.itemIdentifiers { - persistentContainer.status(for: id)?.decrementReferenceCount() - } - } - override func viewDidLoad() { super.viewDidLoad() @@ -149,7 +141,7 @@ class ConversationTableViewController: EnhancedTableViewController { switch response { case let .success(status, _): let viewContext = self.mastodonController.persistentContainer.viewContext - self.mastodonController.persistentContainer.addOrUpdate(status: status, incrementReferenceCount: false, context: viewContext) { (statusMO) in + self.mastodonController.persistentContainer.addOrUpdate(status: status, context: viewContext) { (statusMO) in self.mainStatusLoaded(statusMO) } @@ -169,8 +161,6 @@ class ConversationTableViewController: EnhancedTableViewController { } private func mainStatusLoaded(_ mainStatus: StatusMO) { - mainStatus.incrementReferenceCount() - let mainStatusItem = Item.status(id: mainStatusID, state: mainStatusState) var snapshot = NSDiffableDataSourceSnapshot() diff --git a/Tusker/Screens/Profile/ProfileViewController.swift b/Tusker/Screens/Profile/ProfileViewController.swift index 3e1675f3d1..cb21d512e1 100644 --- a/Tusker/Screens/Profile/ProfileViewController.swift +++ b/Tusker/Screens/Profile/ProfileViewController.swift @@ -56,12 +56,6 @@ class ProfileViewController: UIPageViewController { required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } - - deinit { - if let accountID = accountID { - mastodonController.persistentContainer.account(for: accountID)?.decrementReferenceCount() - } - } override func viewDidLoad() { super.viewDidLoad() @@ -122,7 +116,7 @@ class ProfileViewController: UIPageViewController { guard let self = self else { return } switch response { case .success(let account, _): - self.mastodonController.persistentContainer.addOrUpdate(account: account, incrementReferenceCount: true) { (account) in + self.mastodonController.persistentContainer.addOrUpdate(account: account) { (account) in DispatchQueue.main.async { self.updateAccountUI() } diff --git a/Tusker/Screens/Search/SearchResultsViewController.swift b/Tusker/Screens/Search/SearchResultsViewController.swift index 96b6496f76..01d551bbac 100644 --- a/Tusker/Screens/Search/SearchResultsViewController.swift +++ b/Tusker/Screens/Search/SearchResultsViewController.swift @@ -172,24 +172,9 @@ class SearchResultsViewController: EnhancedTableViewController { } private func showSearchResults(_ results: SearchResults) { - let oldSnapshot = self.dataSource.snapshot() var snapshot = NSDiffableDataSourceSnapshot() self.mastodonController.persistentContainer.performBatchUpdates({ (context, addAccounts, addStatuses) in - if oldSnapshot.indexOfSection(.accounts) != nil { - oldSnapshot.itemIdentifiers(inSection: .accounts).forEach { (item) in - guard case let .account(id) = item else { return } - self.mastodonController.persistentContainer.account(for: id, in: context)?.decrementReferenceCount() - } - } - - if oldSnapshot.indexOfSection(.statuses) != nil { - oldSnapshot.itemIdentifiers(inSection: .statuses).forEach { (item) in - guard case let .status(id, _) = item else { return } - self.mastodonController.persistentContainer.status(for: id, in: context)?.decrementReferenceCount() - } - } - let resultTypes = self.resultTypes if !results.accounts.isEmpty && (resultTypes == nil || resultTypes!.contains(.accounts)) { snapshot.appendSections([.accounts]) diff --git a/Tusker/Screens/Status Action Account List/StatusActionAccountListTableViewController.swift b/Tusker/Screens/Status Action Account List/StatusActionAccountListTableViewController.swift index 040d42d392..45d3b15da4 100644 --- a/Tusker/Screens/Status Action Account List/StatusActionAccountListTableViewController.swift +++ b/Tusker/Screens/Status Action Account List/StatusActionAccountListTableViewController.swift @@ -60,16 +60,6 @@ class StatusActionAccountListTableViewController: EnhancedTableViewController { fatalError("init(coder:) has not been implemented") } - deinit { - if let accountIDs = self.accountIDs, let container = mastodonController?.persistentContainer { - container.backgroundContext.perform { - for id in accountIDs { - container.account(for: id, in: container.backgroundContext)?.decrementReferenceCount() - } - } - } - } - override func viewDidLoad() { super.viewDidLoad() @@ -83,11 +73,7 @@ class StatusActionAccountListTableViewController: EnhancedTableViewController { tableView.tableHeaderView = UIView(frame: CGRect(x: 0, y: 0, width: 0, height: CGFloat.leastNormalMagnitude)) - if let accountIDs = accountIDs { - accountIDs.forEach { (id) in - self.mastodonController.persistentContainer.account(for: id)?.incrementReferenceCount() - } - } else { + if accountIDs == nil { // account IDs haven't been set, so perform a request to load them guard let status = mastodonController.persistentContainer.status(for: statusID) else { fatalError("Missing cached status \(statusID)") diff --git a/Tusker/Screens/Timeline/TimelineTableViewController.swift b/Tusker/Screens/Timeline/TimelineTableViewController.swift index 5f66dc5d80..ba991da67f 100644 --- a/Tusker/Screens/Timeline/TimelineTableViewController.swift +++ b/Tusker/Screens/Timeline/TimelineTableViewController.swift @@ -42,18 +42,6 @@ class TimelineTableViewController: DiffableTimelineLikeTableViewController