From c9fa11cc3bd2b2e1f5a7442af3029b063c2bdde1 Mon Sep 17 00:00:00 2001 From: Shadowfacts Date: Wed, 30 Nov 2022 22:16:33 -0500 Subject: [PATCH] Fetch filters and store in CoreData --- .../Sources/Pachyderm/Model/Filter.swift | 3 +- Tusker.xcodeproj/project.pbxproj | 4 ++ Tusker/API/MastodonController.swift | 16 +++++ Tusker/CoreData/FilterMO.swift | 66 +++++++++++++++++++ .../MastodonCachePersistentStore.swift | 23 +++++++ .../Tusker.xcdatamodel/contents | 8 +++ 6 files changed, 119 insertions(+), 1 deletion(-) create mode 100644 Tusker/CoreData/FilterMO.swift diff --git a/Pachyderm/Sources/Pachyderm/Model/Filter.swift b/Pachyderm/Sources/Pachyderm/Model/Filter.swift index d67d0011c8..92e05fadd2 100644 --- a/Pachyderm/Sources/Pachyderm/Model/Filter.swift +++ b/Pachyderm/Sources/Pachyderm/Model/Filter.swift @@ -8,7 +8,7 @@ import Foundation -public class Filter: Decodable { +public struct Filter: Decodable { public let id: String public let phrase: String private let context: [String] @@ -51,6 +51,7 @@ extension Filter { case notifications case `public` case thread + case account } } diff --git a/Tusker.xcodeproj/project.pbxproj b/Tusker.xcodeproj/project.pbxproj index fc9bbc7b4e..d5a00af074 100644 --- a/Tusker.xcodeproj/project.pbxproj +++ b/Tusker.xcodeproj/project.pbxproj @@ -52,6 +52,7 @@ D61F759229365C6C00C0B37F /* CollectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61F759129365C6C00C0B37F /* CollectionViewController.swift */; }; D61F75942936F0DA00C0B37F /* FollowedHashtag.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61F75932936F0DA00C0B37F /* FollowedHashtag.swift */; }; D61F75962937037800C0B37F /* ToggleFollowHashtagService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61F75952937037800C0B37F /* ToggleFollowHashtagService.swift */; }; + D61F759B29384F9C00C0B37F /* FilterMO.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61F759A29384F9C00C0B37F /* FilterMO.swift */; }; D620483423D3801D008A63EF /* LinkTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D620483323D3801D008A63EF /* LinkTextView.swift */; }; D620483623D38075008A63EF /* ContentTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D620483523D38075008A63EF /* ContentTextView.swift */; }; D620483823D38190008A63EF /* StatusContentTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D620483723D38190008A63EF /* StatusContentTextView.swift */; }; @@ -420,6 +421,7 @@ D61F759129365C6C00C0B37F /* CollectionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectionViewController.swift; sourceTree = ""; }; D61F75932936F0DA00C0B37F /* FollowedHashtag.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowedHashtag.swift; sourceTree = ""; }; D61F75952937037800C0B37F /* ToggleFollowHashtagService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToggleFollowHashtagService.swift; sourceTree = ""; }; + D61F759A29384F9C00C0B37F /* FilterMO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilterMO.swift; sourceTree = ""; }; D620483323D3801D008A63EF /* LinkTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkTextView.swift; sourceTree = ""; }; D620483523D38075008A63EF /* ContentTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentTextView.swift; sourceTree = ""; }; D620483723D38190008A63EF /* StatusContentTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusContentTextView.swift; sourceTree = ""; }; @@ -886,6 +888,7 @@ D6B9366E2828452F00237D0E /* SavedHashtag.swift */, D6B9366C2828444F00237D0E /* SavedInstance.swift */, D61F75932936F0DA00C0B37F /* FollowedHashtag.swift */, + D61F759A29384F9C00C0B37F /* FilterMO.swift */, D60E2F2D244248BF005F8713 /* MastodonCachePersistentStore.swift */, D6B936702829F72900237D0E /* NSManagedObjectContext+Helpers.swift */, ); @@ -1824,6 +1827,7 @@ D69693FA25859A8000F4E116 /* ComposeSceneDelegate.swift in Sources */, D6A3BC8A2321F79B00FD64D5 /* AccountTableViewCell.swift in Sources */, D66A77BB233838DC0058F1EC /* UIFont+Traits.swift in Sources */, + D61F759B29384F9C00C0B37F /* FilterMO.swift in Sources */, D68FEC4F232C5BC300C84F23 /* SegmentedPageViewController.swift in Sources */, D64AAE9126C80DC600FC57FB /* ToastView.swift in Sources */, D62275AA24F1E01C00B82A16 /* ComposeTextView.swift in Sources */, diff --git a/Tusker/API/MastodonController.swift b/Tusker/API/MastodonController.swift index 5343b76544..2b2b3a7403 100644 --- a/Tusker/API/MastodonController.swift +++ b/Tusker/API/MastodonController.swift @@ -50,6 +50,7 @@ class MastodonController: ObservableObject { @Published private(set) var lists: [List] = [] @Published private(set) var customEmojis: [Emoji]? @Published private(set) var followedHashtags: [FollowedHashtag] = [] + @Published private(set) var filters: [FilterMO] = [] private var cancellables = Set() @@ -154,6 +155,7 @@ class MastodonController: ObservableObject { _ = try await (ownAccount, ownInstance) loadLists() + async let _ = await loadFilters() } func getOwnAccount(completion: ((Result) -> Void)? = nil) { @@ -350,6 +352,20 @@ class MastodonController: ObservableObject { followedHashtags = (try? persistentContainer.viewContext.fetch(FollowedHashtag.fetchRequest())) ?? [] } + @MainActor + func loadFilters() async { + filters = (try? persistentContainer.viewContext.fetch(FilterMO.fetchRequest())) ?? [] + + let req = Client.getFilters() + if let (filters, _) = try? await run(req) { + self.persistentContainer.updateFilters(filters) { + if case .success(let filters) = $0 { + self.filters = filters + } + } + } + } + } private struct ListComparator: SortComparator { diff --git a/Tusker/CoreData/FilterMO.swift b/Tusker/CoreData/FilterMO.swift new file mode 100644 index 0000000000..af40620b7c --- /dev/null +++ b/Tusker/CoreData/FilterMO.swift @@ -0,0 +1,66 @@ +// +// FilterMO.swift +// Tusker +// +// Created by Shadowfacts on 11/30/22. +// Copyright © 2022 Shadowfacts. All rights reserved. +// + +import Foundation +import CoreData +import Pachyderm + +@objc(FilterMO) +public final class FilterMO: NSManagedObject { + + @nonobjc public class func fetchRequest() -> NSFetchRequest { + return NSFetchRequest(entityName: "Filter") + } + + @NSManaged public var id: String + @NSManaged public var phrase: String + @NSManaged private var context: String + @NSManaged public var expiresAt: Date? + @NSManaged public var irreversible: Bool + @NSManaged public var wholeWord: Bool + + private var _contexts: [Filter.Context]? + var contexts: [Filter.Context] { + get { + if let _contexts { + return _contexts + } else { + _contexts = context.split(separator: ",").compactMap { .init(rawValue: String($0)) } + return _contexts! + } + } + set { + _contexts = newValue + context = newValue.map(\.rawValue).joined(separator: ",") + } + } + + public override func didChangeValue(forKey key: String) { + super.didChangeValue(forKey: key) + if key == "context" { + _contexts = nil + } + } + +} + +extension FilterMO { + convenience init(apiFilter filter: Filter, context: NSManagedObjectContext) { + self.init(context: context) + self.updateFrom(apiFilter: filter) + } + + func updateFrom(apiFilter filter: Filter) { + self.id = filter.id + self.phrase = filter.phrase + self.contexts = filter.contexts + self.expiresAt = filter.expiresAt + self.irreversible = filter.irreversible + self.wholeWord = filter.wholeWord + } +} diff --git a/Tusker/CoreData/MastodonCachePersistentStore.swift b/Tusker/CoreData/MastodonCachePersistentStore.swift index 2fc5938c2b..f51446e40a 100644 --- a/Tusker/CoreData/MastodonCachePersistentStore.swift +++ b/Tusker/CoreData/MastodonCachePersistentStore.swift @@ -299,6 +299,29 @@ class MastodonCachePersistentStore: NSPersistentContainer { } } + func updateFilters(_ filters: [Filter], completion: @escaping (Result<[FilterMO], Error>) -> Void) { + viewContext.perform { + do { + var all = try self.viewContext.fetch(FilterMO.fetchRequest()) + + let toDelete = all.filter { existing in !filters.contains(where: { $0.id == existing.id }) }.map(\.objectID) + if !toDelete.isEmpty { + try self.viewContext.execute(NSBatchDeleteRequest(objectIDs: toDelete)) + } + + for filter in filters where !all.contains(where: { $0.id == filter.id }) { + let mo = FilterMO(apiFilter: filter, context: self.viewContext) + all.append(mo) + } + + self.save(context: self.viewContext) + completion(.success(all)) + } catch { + completion(.failure(error)) + } + } + } + @objc private func managedObjectsDidChange(_ notification: Foundation.Notification) { let changes = hasChangedSavedHashtagsOrInstances(notification) if changes.hashtags { diff --git a/Tusker/CoreData/Tusker.xcdatamodeld/Tusker.xcdatamodel/contents b/Tusker/CoreData/Tusker.xcdatamodeld/Tusker.xcdatamodel/contents index 5c636a15fd..82324f01ea 100644 --- a/Tusker/CoreData/Tusker.xcdatamodeld/Tusker.xcdatamodel/contents +++ b/Tusker/CoreData/Tusker.xcdatamodeld/Tusker.xcdatamodel/contents @@ -28,6 +28,14 @@ + + + + + + + +