Remove private-note and notification from user (IOS-192)

This commit is contained in:
Nathan Mattes 2024-01-05 16:50:44 +01:00
parent 7ab194b15d
commit 0b959f5bca
13 changed files with 0 additions and 708 deletions

View File

@ -7,7 +7,6 @@
import UIKit
import CoreDataStack
import class CoreDataStack.Notification
import MastodonCore
import MastodonSDK
import MastodonLocalization

View File

@ -9,7 +9,6 @@
import UIKit
import CoreDataStack
import MastodonSDK
import class CoreDataStack.Notification
enum DataSourceItem: Hashable {
case status(record: MastodonStatus)

View File

@ -15,7 +15,6 @@ import Meta
import MastodonAsset
import MastodonCore
import MastodonLocalization
import class CoreDataStack.Notification
import MastodonSDK
extension NotificationView {

View File

@ -53,7 +53,6 @@
<attribute name="isLoadingMore" transient="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="kindRaw" attributeType="String"/>
<attribute name="updatedAt" attributeType="Date" usesScalarValueType="NO"/>
<relationship name="notification" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Notification" inverseName="feeds" inverseEntity="Notification"/>
<relationship name="status" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Status" inverseName="feeds" inverseEntity="Status"/>
</entity>
<entity name="Instance" representedClassName="CoreDataStack.Instance" syncable="YES">
@ -119,10 +118,7 @@
<relationship name="muted" toMany="YES" deletionRule="Nullify" destinationEntity="Status" inverseName="mutedBy" inverseEntity="Status"/>
<relationship name="muting" toMany="YES" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="mutingBy" inverseEntity="MastodonUser"/>
<relationship name="mutingBy" toMany="YES" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="muting" inverseEntity="MastodonUser"/>
<relationship name="notifications" toMany="YES" deletionRule="Nullify" destinationEntity="Notification" inverseName="account" inverseEntity="Notification"/>
<relationship name="pinnedStatus" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Status" inverseName="pinnedBy" inverseEntity="Status"/>
<relationship name="privateNotes" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="PrivateNote" inverseName="to" inverseEntity="PrivateNote"/>
<relationship name="privateNotesTo" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="PrivateNote" inverseName="from" inverseEntity="PrivateNote"/>
<relationship name="reblogged" toMany="YES" deletionRule="Nullify" destinationEntity="Status" inverseName="rebloggedBy" inverseEntity="Status"/>
<relationship name="showingReblogs" toMany="YES" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="showingReblogsBy" inverseEntity="MastodonUser"/>
<relationship name="showingReblogsBy" toMany="YES" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="showingReblogs" inverseEntity="MastodonUser"/>
@ -130,19 +126,6 @@
<relationship name="votePollOptions" toMany="YES" deletionRule="Nullify" destinationEntity="PollOption" inverseName="votedBy" inverseEntity="PollOption"/>
<relationship name="votePolls" toMany="YES" deletionRule="Nullify" destinationEntity="Poll" inverseName="votedBy" inverseEntity="Poll"/>
</entity>
<entity name="Notification" representedClassName="CoreDataStack.Notification" syncable="YES">
<attribute name="createAt" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="domain" attributeType="String"/>
<attribute name="followRequestState" optional="YES" attributeType="Binary"/>
<attribute name="id" attributeType="String"/>
<attribute name="transientFollowRequestState" optional="YES" transient="YES" attributeType="Binary"/>
<attribute name="typeRaw" attributeType="String"/>
<attribute name="updatedAt" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="userID" attributeType="String"/>
<relationship name="account" maxCount="1" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="notifications" inverseEntity="MastodonUser"/>
<relationship name="feeds" toMany="YES" deletionRule="Cascade" destinationEntity="Feed" inverseName="notification" inverseEntity="Feed"/>
<relationship name="status" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Status" inverseName="notifications" inverseEntity="Status"/>
</entity>
<entity name="Poll" representedClassName="CoreDataStack.Poll" syncable="YES">
<attribute name="createdAt" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="domain" attributeType="String" defaultValueString=""/>
@ -168,12 +151,6 @@
<relationship name="poll" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Poll" inverseName="options" inverseEntity="Poll"/>
<relationship name="votedBy" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="votePollOptions" inverseEntity="MastodonUser"/>
</entity>
<entity name="PrivateNote" representedClassName="CoreDataStack.PrivateNote" syncable="YES">
<attribute name="note" optional="YES" attributeType="String"/>
<attribute name="updatedAt" attributeType="Date" usesScalarValueType="NO"/>
<relationship name="from" maxCount="1" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="privateNotesTo" inverseEntity="MastodonUser"/>
<relationship name="to" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="privateNotes" inverseEntity="MastodonUser"/>
</entity>
<entity name="Setting" representedClassName="CoreDataStack.Setting" syncable="YES">
<attribute name="createdAt" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="domain" attributeType="String"/>
@ -216,7 +193,6 @@
<relationship name="favouritedBy" toMany="YES" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="favourite" inverseEntity="MastodonUser"/>
<relationship name="feeds" toMany="YES" deletionRule="Cascade" destinationEntity="Feed" inverseName="status" inverseEntity="Feed"/>
<relationship name="mutedBy" toMany="YES" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="muted" inverseEntity="MastodonUser"/>
<relationship name="notifications" toMany="YES" deletionRule="Cascade" destinationEntity="Notification" inverseName="status" inverseEntity="Notification"/>
<relationship name="pinnedBy" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="MastodonUser" inverseName="pinnedStatus" inverseEntity="MastodonUser"/>
<relationship name="poll" optional="YES" maxCount="1" deletionRule="Cascade" destinationEntity="Poll" inverseName="status" inverseEntity="Poll"/>
<relationship name="reblog" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Status" inverseName="reblogFrom" inverseEntity="Status"/>

View File

@ -96,19 +96,6 @@ extension Feed {
public static func hasNotificationPredicate() -> NSPredicate {
return NSPredicate(format: "%K != nil", #keyPath(Feed.notification))
}
public static func notificationTypePredicate(types: [MastodonNotificationType]) -> NSPredicate {
return NSCompoundPredicate(andPredicateWithSubpredicates: [
hasNotificationPredicate(),
NSPredicate(
format: "%K.%K IN %@",
#keyPath(Feed.notification),
#keyPath(Notification.typeRaw),
types.map { $0.rawValue }
)
])
}
}
// MARK: - AutoGenerateProperty

View File

@ -66,7 +66,6 @@ final public class MastodonUser: NSManagedObject {
// one-to-many relationship
@NSManaged public private(set) var statuses: Set<Status>
@NSManaged public private(set) var notifications: Set<Notification>
// many-to-many relationship
@NSManaged public private(set) var favourite: Set<Status>

View File

@ -1,269 +0,0 @@
//
// Notification.swift
// CoreDataStack
//
// Created by sxiaojian on 2021/4/13.
//
import Foundation
import CoreData
public final class Notification: NSManagedObject {
public typealias ID = String
// sourcery: autoGenerateProperty
@NSManaged public private(set) var id: ID
// sourcery: autoGenerateProperty
@NSManaged public private(set) var typeRaw: String
// sourcery: autoGenerateProperty
@NSManaged public private(set) var domain: String
// sourcery: autoGenerateProperty
@NSManaged public private(set) var userID: String
// sourcery: autoGenerateProperty
@NSManaged public private(set) var createAt: Date
// sourcery: autoUpdatableObject, autoGenerateProperty
@NSManaged public private(set) var updatedAt: Date
// one-to-one relationship
// sourcery: autoGenerateRelationship
@NSManaged public private(set) var account: MastodonUser
// sourcery: autoGenerateRelationship
@NSManaged public private(set) var status: Status?
// many-to-one relationship
@NSManaged public private(set) var feeds: Set<Feed>
}
extension Notification {
// sourcery: autoUpdatableObject
@objc public var followRequestState: MastodonFollowRequestState {
get {
let keyPath = #keyPath(Notification.followRequestState)
willAccessValue(forKey: keyPath)
let _data = primitiveValue(forKey: keyPath) as? Data
didAccessValue(forKey: keyPath)
do {
guard let data = _data, !data.isEmpty else { return .init(state: .none) }
let state = try JSONDecoder().decode(MastodonFollowRequestState.self, from: data)
return state
} catch {
assertionFailure(error.localizedDescription)
return .init(state: .none)
}
}
set {
let keyPath = #keyPath(Notification.followRequestState)
let data = try? JSONEncoder().encode(newValue)
willChangeValue(forKey: keyPath)
setPrimitiveValue(data, forKey: keyPath)
didChangeValue(forKey: keyPath)
}
}
// sourcery: autoUpdatableObject
@objc public var transientFollowRequestState: MastodonFollowRequestState {
get {
let keyPath = #keyPath(Notification.transientFollowRequestState)
willAccessValue(forKey: keyPath)
let _data = primitiveValue(forKey: keyPath) as? Data
didAccessValue(forKey: keyPath)
do {
guard let data = _data else { return .init(state: .none) }
let state = try JSONDecoder().decode(MastodonFollowRequestState.self, from: data)
return state
} catch {
assertionFailure(error.localizedDescription)
return .init(state: .none)
}
}
set {
let keyPath = #keyPath(Notification.transientFollowRequestState)
let data = try? JSONEncoder().encode(newValue)
willChangeValue(forKey: keyPath)
setPrimitiveValue(data, forKey: keyPath)
didChangeValue(forKey: keyPath)
}
}
}
extension Notification: FeedIndexable { }
extension Notification {
@discardableResult
public static func insert(
into context: NSManagedObjectContext,
property: Property,
relationship: Relationship
) -> Notification {
let object: Notification = context.insertObject()
object.configure(property: property)
object.configure(relationship: relationship)
return object
}
}
extension Notification: Managed {
public static var defaultSortDescriptors: [NSSortDescriptor] {
return [NSSortDescriptor(keyPath: \Notification.createAt, ascending: false)]
}
}
extension Notification {
static func predicate(domain: String) -> NSPredicate {
return NSPredicate(format: "%K == %@", #keyPath(Notification.domain), domain)
}
static func predicate(userID: String) -> NSPredicate {
return NSPredicate(format: "%K == %@", #keyPath(Notification.userID), userID)
}
static func predicate(id: ID) -> NSPredicate {
return NSPredicate(format: "%K == %@", #keyPath(Notification.id), id)
}
static func predicate(typeRaw: String) -> NSPredicate {
return NSPredicate(format: "%K == %@", #keyPath(Notification.typeRaw), typeRaw)
}
public static func predicate(
domain: String,
userID: String,
id: ID
) -> NSPredicate {
return NSCompoundPredicate(andPredicateWithSubpredicates: [
Notification.predicate(domain: domain),
Notification.predicate(userID: userID),
Notification.predicate(id: id)
])
}
public static func predicate(
domain: String,
userID: String,
typeRaw: String? = nil
) -> NSPredicate {
if let typeRaw = typeRaw {
return NSCompoundPredicate(andPredicateWithSubpredicates: [
Notification.predicate(domain: domain),
Notification.predicate(typeRaw: typeRaw),
Notification.predicate(userID: userID),
])
} else {
return NSCompoundPredicate(andPredicateWithSubpredicates: [
Notification.predicate(domain: domain),
Notification.predicate(userID: userID)
])
}
}
public static func predicate(validTypesRaws types: [String]) -> NSPredicate {
return NSPredicate(format: "%K IN %@", #keyPath(Notification.typeRaw), types)
}
}
// MARK: - AutoGenerateProperty
extension Notification: AutoGenerateProperty {
// sourcery:inline:Notification.AutoGenerateProperty
// Generated using Sourcery
// DO NOT EDIT
public struct Property {
public let id: ID
public let typeRaw: String
public let domain: String
public let userID: String
public let createAt: Date
public let updatedAt: Date
public init(
id: ID,
typeRaw: String,
domain: String,
userID: String,
createAt: Date,
updatedAt: Date
) {
self.id = id
self.typeRaw = typeRaw
self.domain = domain
self.userID = userID
self.createAt = createAt
self.updatedAt = updatedAt
}
}
public func configure(property: Property) {
self.id = property.id
self.typeRaw = property.typeRaw
self.domain = property.domain
self.userID = property.userID
self.createAt = property.createAt
self.updatedAt = property.updatedAt
}
public func update(property: Property) {
update(updatedAt: property.updatedAt)
}
// sourcery:end
}
// MARK: - AutoGenerateRelationship
extension Notification: AutoGenerateRelationship {
// sourcery:inline:Notification.AutoGenerateRelationship
// Generated using Sourcery
// DO NOT EDIT
public struct Relationship {
public let account: MastodonUser
public let status: Status?
public init(
account: MastodonUser,
status: Status?
) {
self.account = account
self.status = status
}
}
public func configure(relationship: Relationship) {
self.account = relationship.account
self.status = relationship.status
}
// sourcery:end
}
// MARK: - AutoUpdatableObject
extension Notification: AutoUpdatableObject {
// sourcery:inline:Notification.AutoUpdatableObject
// Generated using Sourcery
// DO NOT EDIT
public func update(updatedAt: Date) {
if self.updatedAt != updatedAt {
self.updatedAt = updatedAt
}
}
public func update(followRequestState: MastodonFollowRequestState) {
if self.followRequestState != followRequestState {
self.followRequestState = followRequestState
}
}
public func update(transientFollowRequestState: MastodonFollowRequestState) {
if self.transientFollowRequestState != transientFollowRequestState {
self.transientFollowRequestState = transientFollowRequestState
}
}
// sourcery:end
}
extension Notification {
public func attach(feed: Feed) {
mutableSetValue(forKey: #keyPath(Notification.feeds)).add(feed)
}
}

View File

@ -1,56 +0,0 @@
//
// PrivateNote.swift
// CoreDataStack
//
// Created by MainasuK Cirno on 2021-4-1.
//
import CoreData
import Foundation
final public class PrivateNote: NSManagedObject {
@NSManaged public private(set) var note: String?
@NSManaged public private(set) var updatedAt: Date
// many-to-one relationship
@NSManaged public private(set) var to: MastodonUser?
@NSManaged public private(set) var from: MastodonUser
}
extension PrivateNote {
public override func awakeFromInsert() {
super.awakeFromInsert()
setPrimitiveValue(Date(), forKey: #keyPath(PrivateNote.updatedAt))
}
@discardableResult
public static func insert(
into context: NSManagedObjectContext,
property: Property
) -> PrivateNote {
let privateNode: PrivateNote = context.insertObject()
privateNode.note = property.note
return privateNode
}
}
extension PrivateNote {
public struct Property {
public let note: String?
init(note: String) {
self.note = note
}
}
}
extension PrivateNote: Managed {
public static var defaultSortDescriptors: [NSSortDescriptor] {
return [NSSortDescriptor(keyPath: \PrivateNote.updatedAt, ascending: false)]
}
}

View File

@ -1,29 +0,0 @@
//
// Notification+Property.swift
// Mastodon
//
// Created by MainasuK on 2022-1-21.
//
import Foundation
import CoreDataStack
import MastodonSDK
import class CoreDataStack.Notification
extension Notification.Property {
public init(
entity: Mastodon.Entity.Notification,
domain: String,
userID: String,
networkDate: Date
) {
self.init(
id: entity.id,
typeRaw: entity.type.rawValue,
domain: domain,
userID: userID,
createAt: entity.createdAt,
updatedAt: networkDate
)
}
}

View File

@ -1,197 +0,0 @@
//
// Persistence+Notification.swift
// Mastodon
//
// Created by MainasuK on 2022-1-21.
//
import CoreData
import CoreDataStack
import Foundation
import MastodonSDK
import class CoreDataStack.Notification
extension Persistence.Notification {
public struct PersistContext {
public let domain: String
public let entity: Mastodon.Entity.Notification
public let me: MastodonUser
public let networkDate: Date
public init(
domain: String,
entity: Mastodon.Entity.Notification,
me: MastodonUser,
networkDate: Date
) {
self.domain = domain
self.entity = entity
self.me = me
self.networkDate = networkDate
}
}
public struct PersistResult {
public let notification: Notification
public let isNewInsertion: Bool
public init(
notification: Notification,
isNewInsertion: Bool
) {
self.notification = notification
self.isNewInsertion = isNewInsertion
}
}
public static func createOrMerge(
in managedObjectContext: NSManagedObjectContext,
context: PersistContext
) -> PersistResult {
if let old = fetch(in: managedObjectContext, context: context) {
merge(object: old, context: context)
return PersistResult(
notification: old,
isNewInsertion: false
)
} else {
let accountResult = Persistence.MastodonUser.createOrMerge(
in: managedObjectContext,
context: Persistence.MastodonUser.PersistContext(
domain: context.domain,
entity: context.entity.account,
cache: nil,
networkDate: context.networkDate
)
)
let account = accountResult.user
let status: Status? = {
guard let entity = context.entity.status else { return nil }
let result = Persistence.Status.createOrMerge(
in: managedObjectContext,
context: Persistence.Status.PersistContext(
domain: context.domain,
entity: entity,
me: context.me,
statusCache: nil,
userCache: nil,
networkDate: context.networkDate
)
)
return result.status
}()
let relationship = Notification.Relationship(
account: account,
status: status
)
let object = create(
in: managedObjectContext,
context: context,
relationship: relationship
)
return PersistResult(
notification: object,
isNewInsertion: true
)
}
}
}
extension Persistence.Notification {
public static func fetch(
in managedObjectContext: NSManagedObjectContext,
context: PersistContext
) -> Notification? {
let request = Notification.sortedFetchRequest
request.predicate = Notification.predicate(
domain: context.me.domain,
userID: context.me.id,
id: context.entity.id
)
request.fetchLimit = 1
do {
return try managedObjectContext.fetch(request).first
} catch {
assertionFailure(error.localizedDescription)
return nil
}
}
@discardableResult
public static func create(
in managedObjectContext: NSManagedObjectContext,
context: PersistContext,
relationship: Notification.Relationship
) -> Notification {
let property = Notification.Property(
entity: context.entity,
domain: context.me.domain,
userID: context.me.id,
networkDate: context.networkDate
)
let object = Notification.insert(
into: managedObjectContext,
property: property,
relationship: relationship
)
update(object: object, context: context)
return object
}
public static func merge(
object: Notification,
context: PersistContext
) {
guard context.networkDate > object.updatedAt else { return }
let property = Notification.Property(
entity: context.entity,
domain: context.me.domain,
userID: context.me.id,
networkDate: context.networkDate
)
object.update(property: property)
if let status = object.status, let entity = context.entity.status {
let property = Status.Property(
entity: entity,
domain: context.domain,
networkDate: context.networkDate
)
status.update(property: property)
}
let accountProperty = MastodonUser.Property(
entity: context.entity.account,
domain: context.domain,
networkDate: context.networkDate
)
object.account.update(property: accountProperty)
if let author = object.status, let entity = context.entity.status {
let property = Status.Property(
entity: entity,
domain: context.domain,
networkDate: context.networkDate
)
author.update(property: property)
}
update(object: object, context: context)
}
private static func update(
object: Notification,
context: PersistContext
) {
// do nothing
}
}

View File

@ -68,21 +68,3 @@ extension APIService {
return response
}
}
extension MastodonUser {
func deleteStatusAndNotificationFeeds(in context: NSManagedObjectContext) {
statuses.map {
$0.feeds
.union($0.reblogFrom.map { $0.feeds }.flatMap { $0 })
.union($0.notifications.map { $0.feeds }.flatMap { $0 })
}
.flatMap { $0 }
.forEach(context.delete)
notifications.map {
$0.feeds
}
.flatMap { $0 }
.forEach(context.delete)
}
}

View File

@ -40,21 +40,6 @@ extension APIService {
authorization: authenticationBox.userAuthorization
).singleOutput()
let userIDs = response.value.map { $0.id }
let predicate = MastodonUser.predicate(domain: authenticationBox.domain, ids: userIDs)
let fetchRequest = MastodonUser.fetchRequest()
fetchRequest.predicate = predicate
fetchRequest.includesPropertyValues = false
try await managedObjectContext.performChanges {
let users = try managedObjectContext.fetch(fetchRequest) as! [MastodonUser]
for user in users {
user.deleteStatusAndNotificationFeeds(in: managedObjectContext)
}
}
return response
}

View File

@ -11,7 +11,6 @@ import CoreDataStack
import Foundation
import MastodonSDK
import OSLog
import class CoreDataStack.Notification
extension APIService {
@ -86,74 +85,6 @@ extension APIService {
authorization: authorization
).singleOutput()
let managedObjectContext = self.backgroundManagedObjectContext
try await managedObjectContext.performChanges {
guard let me = authenticationBox.authentication.user(in: managedObjectContext) else {
assertionFailure()
return
}
var notifications: [Notification] = []
for entity in response.value {
let result = Persistence.Notification.createOrMerge(
in: managedObjectContext,
context: Persistence.Notification.PersistContext(
domain: authenticationBox.domain,
entity: entity,
me: me,
networkDate: response.networkDate
)
)
notifications.append(result.notification)
}
// locate anchor notification
let anchorNotification: Notification? = {
guard let maxID = query.maxID else { return nil }
let request = Notification.sortedFetchRequest
request.predicate = Notification.predicate(
domain: authenticationBox.domain,
userID: authenticationBox.userID,
id: maxID
)
request.fetchLimit = 1
return try? managedObjectContext.fetch(request).first
}()
// update hasMore flag for anchor status
let acct = Feed.Acct.mastodon(domain: authenticationBox.domain, userID: authenticationBox.userID)
let kind: Feed.Kind = scope == .everything ? .notificationAll : .notificationMentions
if let anchorNotification = anchorNotification,
let feed = anchorNotification.feed(kind: kind, acct: acct) {
feed.update(hasMore: false)
}
// persist Feed relationship
let sortedNotifications = notifications.sorted(by: { $0.createAt < $1.createAt })
let oldestNotification = sortedNotifications.first
for notification in notifications {
let _feed = notification.feed(kind: kind, acct: acct)
if let feed = _feed {
feed.update(updatedAt: response.networkDate)
} else {
let feedProperty = Feed.Property(
acct: acct,
kind: kind,
hasMore: false,
createdAt: notification.createAt,
updatedAt: response.networkDate
)
let feed = Feed.insert(into: managedObjectContext, property: feedProperty)
notification.attach(feed: feed)
// set hasMore on oldest notification if is new feed
if notification === oldestNotification {
feed.update(hasMore: true)
}
}
}
}
return response
}
}
@ -174,20 +105,6 @@ extension APIService {
authorization: authorization
).singleOutput()
let managedObjectContext = self.backgroundManagedObjectContext
try await managedObjectContext.performChanges {
guard let me = authenticationBox.authentication.user(in: managedObjectContext) else { return }
_ = Persistence.Notification.createOrMerge(
in: managedObjectContext,
context: Persistence.Notification.PersistContext(
domain: domain,
entity: response.value,
me: me,
networkDate: response.networkDate
)
)
}
return response
}