diff --git a/Fervor/Package.swift b/Fervor/Package.swift index 58cb5b5..373fdb0 100644 --- a/Fervor/Package.swift +++ b/Fervor/Package.swift @@ -5,6 +5,9 @@ import PackageDescription let package = Package( name: "Fervor", + platforms: [ + .iOS(.v15), + ], products: [ // Products define the executables and libraries a package produces, and make them visible to other packages. .library( diff --git a/Persistence/.gitignore b/Persistence/.gitignore new file mode 100644 index 0000000..3b29812 --- /dev/null +++ b/Persistence/.gitignore @@ -0,0 +1,9 @@ +.DS_Store +/.build +/Packages +/*.xcodeproj +xcuserdata/ +DerivedData/ +.swiftpm/config/registries.json +.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata +.netrc diff --git a/Persistence/Package.swift b/Persistence/Package.swift new file mode 100644 index 0000000..b844ebd --- /dev/null +++ b/Persistence/Package.swift @@ -0,0 +1,34 @@ +// swift-tools-version: 5.7 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +import PackageDescription + +let package = Package( + name: "Persistence", + platforms: [ + .iOS(.v15), + ], + products: [ + // Products define the executables and libraries a package produces, and make them visible to other packages. + .library( + name: "Persistence", + targets: ["Persistence"]), + ], + dependencies: [ + // Dependencies declare other packages that this package depends on. + // .package(url: /* package url */, from: "1.0.0"), + .package(path: "../Fervor"), + ], + targets: [ + // Targets are the basic building blocks of a package. A target can define a module or a test suite. + // Targets can depend on other targets in this package, and on products in packages this package depends on. + .target( + name: "Persistence", + dependencies: [ + "Fervor", + ]), + .testTarget( + name: "PersistenceTests", + dependencies: ["Persistence"]), + ] +) diff --git a/Persistence/README.md b/Persistence/README.md new file mode 100644 index 0000000..8779393 --- /dev/null +++ b/Persistence/README.md @@ -0,0 +1,3 @@ +# Persistence + +A description of this package. diff --git a/Reader/CoreData/Feed+CoreDataClass.swift b/Persistence/Sources/Persistence/Feed+CoreDataClass.swift similarity index 93% rename from Reader/CoreData/Feed+CoreDataClass.swift rename to Persistence/Sources/Persistence/Feed+CoreDataClass.swift index a197c88..9c07cc8 100644 --- a/Reader/CoreData/Feed+CoreDataClass.swift +++ b/Persistence/Sources/Persistence/Feed+CoreDataClass.swift @@ -13,7 +13,7 @@ import Fervor @objc(Feed) public class Feed: NSManagedObject { - func updateFromServer(_ serverFeed: Fervor.Feed) { + public func updateFromServer(_ serverFeed: Fervor.Feed) { guard self.id == nil || self.id == serverFeed.id else { return } self.id = serverFeed.id self.title = serverFeed.title diff --git a/Reader/CoreData/Feed+CoreDataProperties.swift b/Persistence/Sources/Persistence/Feed+CoreDataProperties.swift similarity index 100% rename from Reader/CoreData/Feed+CoreDataProperties.swift rename to Persistence/Sources/Persistence/Feed+CoreDataProperties.swift diff --git a/Reader/CoreData/Group+CoreDataClass.swift b/Persistence/Sources/Persistence/Group+CoreDataClass.swift similarity index 87% rename from Reader/CoreData/Group+CoreDataClass.swift rename to Persistence/Sources/Persistence/Group+CoreDataClass.swift index 0060e2e..3ea0865 100644 --- a/Reader/CoreData/Group+CoreDataClass.swift +++ b/Persistence/Sources/Persistence/Group+CoreDataClass.swift @@ -13,12 +13,11 @@ import Fervor @objc(Group) public class Group: NSManagedObject { - func updateFromServer(_ serverGroup: Fervor.Group) { + public func updateFromServer(_ serverGroup: Fervor.Group) { guard self.id == nil || self.id == serverGroup.id else { return } self.id = serverGroup.id self.title = serverGroup.title // feeds relationships will be updated after feeds are created in PersistentContainer.sync } - } diff --git a/Reader/CoreData/Group+CoreDataProperties.swift b/Persistence/Sources/Persistence/Group+CoreDataProperties.swift similarity index 100% rename from Reader/CoreData/Group+CoreDataProperties.swift rename to Persistence/Sources/Persistence/Group+CoreDataProperties.swift diff --git a/Reader/CoreData/Item+CoreDataClass.swift b/Persistence/Sources/Persistence/Item+CoreDataClass.swift similarity index 92% rename from Reader/CoreData/Item+CoreDataClass.swift rename to Persistence/Sources/Persistence/Item+CoreDataClass.swift index 49cb497..1c50cd3 100644 --- a/Reader/CoreData/Item+CoreDataClass.swift +++ b/Persistence/Sources/Persistence/Item+CoreDataClass.swift @@ -13,7 +13,7 @@ import Fervor @objc(Item) public class Item: NSManagedObject { - func updateFromServer(_ serverItem: Fervor.Item) { + public func updateFromServer(_ serverItem: Fervor.Item) { guard self.id == nil || self.id == serverItem.id else { return } self.id = serverItem.id self.author = serverItem.author @@ -29,5 +29,4 @@ public class Item: NSManagedObject { } } - } diff --git a/Reader/CoreData/Item+CoreDataProperties.swift b/Persistence/Sources/Persistence/Item+CoreDataProperties.swift similarity index 100% rename from Reader/CoreData/Item+CoreDataProperties.swift rename to Persistence/Sources/Persistence/Item+CoreDataProperties.swift diff --git a/Reader/LocalData.swift b/Persistence/Sources/Persistence/LocalData.swift similarity index 60% rename from Reader/LocalData.swift rename to Persistence/Sources/Persistence/LocalData.swift index 545e52b..7899214 100644 --- a/Reader/LocalData.swift +++ b/Persistence/Sources/Persistence/LocalData.swift @@ -9,16 +9,16 @@ import Foundation import Fervor import CryptoKit -struct LocalData { +public struct LocalData { private init() {} private static let encoder = JSONEncoder() private static let decoder = JSONDecoder() - private static var defaults = UserDefaults(suiteName: "group.net.shadowfacts.Reader")! + public static var defaults = UserDefaults(suiteName: "group.net.shadowfacts.Reader")! - static var accounts: [Account] { + public static var accounts: [Account] { get { guard let data = defaults.data(forKey: "accounts"), let accounts = try? decoder.decode([Account].self, from: data) else { @@ -32,7 +32,7 @@ struct LocalData { } } - static var mostRecentAccountID: Data? { + public static var mostRecentAccountID: Data? { get { return defaults.data(forKey: "mostRecentAccountID") } @@ -41,36 +41,25 @@ struct LocalData { } } - static func mostRecentAccount() -> Account? { + public static func mostRecentAccount() -> Account? { guard let id = mostRecentAccountID else { return nil } return account(with: id) } - static func account(with id: Data) -> Account? { + public static func account(with id: Data) -> Account? { return accounts.first(where: { $0.id == id }) } - static func migrateIfNecessary() { - if let accounts = UserDefaults.standard.object(forKey: "accounts") { - UserDefaults.standard.removeObject(forKey: "accounts") - defaults.set(accounts, forKey: "accounts") - } - if let mostRecentAccountID = UserDefaults.standard.object(forKey: "mostRecentAccountID") { - UserDefaults.standard.removeObject(forKey: "mostRecentAccountID") - defaults.set(mostRecentAccountID, forKey: "mostRecentAccountID") - } - } - - struct Account: Codable { - let id: Data - let instanceURL: URL - let clientID: String - let clientSecret: String - let token: Token + public struct Account: Codable { + public let id: Data + public let instanceURL: URL + public let clientID: String + public let clientSecret: String + public let token: Token - init(instanceURL: URL, clientID: String, clientSecret: String, token: Token) { + public init(instanceURL: URL, clientID: String, clientSecret: String, token: Token) { // we use a hash of instance host and account id rather than random ids so that // user activites can uniquely identify accounts across devices var hasher = SHA256() diff --git a/Reader/CoreData/PersistentContainer.swift b/Persistence/Sources/Persistence/PersistentContainer.swift similarity index 90% rename from Reader/CoreData/PersistentContainer.swift rename to Persistence/Sources/Persistence/PersistentContainer.swift index 8505bad..fc0bf3c 100644 --- a/Reader/CoreData/PersistentContainer.swift +++ b/Persistence/Sources/Persistence/PersistentContainer.swift @@ -10,10 +10,10 @@ import Fervor import OSLog // todo: is this actually sendable? -class PersistentContainer: NSPersistentContainer, @unchecked Sendable { +public class PersistentContainer: NSPersistentContainer, @unchecked Sendable { private static let managedObjectModel: NSManagedObjectModel = { - let url = Bundle.main.url(forResource: "Reader", withExtension: "momd")! + let url = Bundle.module.url(forResource: "Reader", withExtension: "momd")! return NSManagedObjectModel(contentsOf: url)! }() @@ -24,11 +24,9 @@ class PersistentContainer: NSPersistentContainer, @unchecked Sendable { return context }() - weak var fervorController: FervorController? - private let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "PersistentContainer") - init(account: LocalData.Account) { + public init(account: LocalData.Account) { // slashes the base64 string turn into subdirectories which we don't want let name = account.id.base64EncodedString().replacingOccurrences(of: "/", with: "_") super.init(name: name, managedObjectModel: PersistentContainer.managedObjectModel) @@ -68,20 +66,20 @@ class PersistentContainer: NSPersistentContainer, @unchecked Sendable { } @MainActor - func saveViewContext() throws { + public func saveViewContext() throws { if viewContext.hasChanges { try viewContext.save() } } - func lastSyncDate() async throws -> Date? { + public func lastSyncDate() async throws -> Date? { return try await backgroundContext.perform { let state = try self.backgroundContext.fetch(SyncState.fetchRequest()).first return state?.lastSync } } - func updateLastSyncDate(_ date: Date) async throws { + public func updateLastSyncDate(_ date: Date) async throws { try await backgroundContext.perform { if let state = try self.backgroundContext.fetch(SyncState.fetchRequest()).first { state.lastSync = date @@ -95,7 +93,7 @@ class PersistentContainer: NSPersistentContainer, @unchecked Sendable { try await self.saveViewContext() } - func sync(serverGroups: [Fervor.Group], serverFeeds: [Fervor.Feed]) async throws { + public func sync(serverGroups: [Fervor.Group], serverFeeds: [Fervor.Feed]) async throws { try await backgroundContext.perform { let existingGroups = try self.backgroundContext.fetch(Group.fetchRequest()) for group in serverGroups { @@ -130,7 +128,7 @@ class PersistentContainer: NSPersistentContainer, @unchecked Sendable { try await self.saveViewContext() } - func syncItems(_ syncUpdate: ItemsSyncUpdate, setSyncState: @escaping (FervorController.SyncState) -> Void) async throws { + public func syncItems(_ syncUpdate: ItemsSyncUpdate, setProgress: @escaping (_ current: Int, _ total: Int) -> Void) async throws { try await backgroundContext.perform { self.logger.debug("syncItems: deleting \(syncUpdate.delete.count, privacy: .public) items") let deleteReq = Item.fetchRequest() @@ -151,7 +149,7 @@ class PersistentContainer: NSPersistentContainer, @unchecked Sendable { self.logger.debug("syncItems: updating \(existing.count, privacy: .public) items, inserting \(syncUpdate.upsert.count - existing.count, privacy: .public)") // todo: this feels like it'll be slow when there are many items for (index, item) in syncUpdate.upsert.enumerated() { - setSyncState(.updateItems(current: index, total: syncUpdate.upsert.count)) + setProgress(index, syncUpdate.upsert.count) if let existing = existing.first(where: { $0.id == item.id }) { existing.updateFromServer(item) } else { diff --git a/Reader/CoreData/Reader.xcdatamodeld/.xccurrentversion b/Persistence/Sources/Persistence/Reader.xcdatamodeld/.xccurrentversion similarity index 100% rename from Reader/CoreData/Reader.xcdatamodeld/.xccurrentversion rename to Persistence/Sources/Persistence/Reader.xcdatamodeld/.xccurrentversion diff --git a/Reader/CoreData/Reader.xcdatamodeld/Reader.xcdatamodel/contents b/Persistence/Sources/Persistence/Reader.xcdatamodeld/Reader.xcdatamodel/contents similarity index 94% rename from Reader/CoreData/Reader.xcdatamodeld/Reader.xcdatamodel/contents rename to Persistence/Sources/Persistence/Reader.xcdatamodeld/Reader.xcdatamodel/contents index e351ea0..eecde3a 100644 --- a/Reader/CoreData/Reader.xcdatamodeld/Reader.xcdatamodel/contents +++ b/Persistence/Sources/Persistence/Reader.xcdatamodeld/Reader.xcdatamodel/contents @@ -1,5 +1,5 @@ - + diff --git a/Reader.xcodeproj/project.pbxproj b/Reader.xcodeproj/project.pbxproj index 7b9020f..265e25d 100644 --- a/Reader.xcodeproj/project.pbxproj +++ b/Reader.xcodeproj/project.pbxproj @@ -10,7 +10,6 @@ D608238D27DE729E00D7D5F9 /* ItemListType.swift in Sources */ = {isa = PBXBuildFile; fileRef = D608238C27DE729E00D7D5F9 /* ItemListType.swift */; }; D65B18B22750469D004A9448 /* LoginViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D65B18B12750469D004A9448 /* LoginViewController.swift */; }; D65B18B627504920004A9448 /* FervorController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D65B18B527504920004A9448 /* FervorController.swift */; }; - D65B18BE275051A1004A9448 /* LocalData.swift in Sources */ = {isa = PBXBuildFile; fileRef = D65B18BD275051A1004A9448 /* LocalData.swift */; }; D65B18C127505348004A9448 /* HomeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D65B18C027505348004A9448 /* HomeViewController.swift */; }; D68408E827947D0800E327D2 /* PrefsSceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D68408E727947D0800E327D2 /* PrefsSceneDelegate.swift */; }; D68408ED2794803D00E327D2 /* PrefsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D68408EC2794803D00E327D2 /* PrefsView.swift */; }; @@ -21,22 +20,14 @@ D68B303D2792204B00E8B3FA /* read.js in Resources */ = {isa = PBXBuildFile; fileRef = D68B303C2792204B00E8B3FA /* read.js */; }; D68B30402792729A00E8B3FA /* AppSplitViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D68B303F2792729A00E8B3FA /* AppSplitViewController.swift */; }; D68B304227932ED500E8B3FA /* UserActivities.swift in Sources */ = {isa = PBXBuildFile; fileRef = D68B304127932ED500E8B3FA /* UserActivities.swift */; }; - D6A8A33427766C2800CCEC72 /* PersistentContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6A8A33327766C2800CCEC72 /* PersistentContainer.swift */; }; D6C687EC272CD27600874C10 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C687EB272CD27600874C10 /* AppDelegate.swift */; }; D6C687EE272CD27600874C10 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C687ED272CD27600874C10 /* SceneDelegate.swift */; }; - D6C687F6272CD27600874C10 /* Reader.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = D6C687F4272CD27600874C10 /* Reader.xcdatamodeld */; }; D6C687F8272CD27700874C10 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D6C687F7272CD27700874C10 /* Assets.xcassets */; }; D6C687FB272CD27700874C10 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D6C687F9272CD27700874C10 /* LaunchScreen.storyboard */; }; D6C68806272CD27700874C10 /* ReaderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C68805272CD27700874C10 /* ReaderTests.swift */; }; D6C68810272CD27700874C10 /* ReaderUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C6880F272CD27700874C10 /* ReaderUITests.swift */; }; D6C68812272CD27700874C10 /* ReaderUITestsLaunchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6C68811272CD27700874C10 /* ReaderUITestsLaunchTests.swift */; }; D6E2434C278B456A0005E546 /* ItemsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E2434B278B456A0005E546 /* ItemsViewController.swift */; }; - D6E24357278B96E40005E546 /* Feed+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E24355278B96E40005E546 /* Feed+CoreDataClass.swift */; }; - D6E24358278B96E40005E546 /* Feed+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E24356278B96E40005E546 /* Feed+CoreDataProperties.swift */; }; - D6E2435D278B97240005E546 /* Item+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E24359278B97240005E546 /* Item+CoreDataClass.swift */; }; - D6E2435E278B97240005E546 /* Item+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E2435A278B97240005E546 /* Item+CoreDataProperties.swift */; }; - D6E2435F278B97240005E546 /* Group+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E2435B278B97240005E546 /* Group+CoreDataClass.swift */; }; - D6E24360278B97240005E546 /* Group+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E2435C278B97240005E546 /* Group+CoreDataProperties.swift */; }; D6E24363278BA1410005E546 /* ItemCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E24361278BA1410005E546 /* ItemCollectionViewCell.swift */; }; D6E24369278BABB40005E546 /* UIColor+App.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E24368278BABB40005E546 /* UIColor+App.swift */; }; D6E2436B278BB1880005E546 /* HomeCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E2436A278BB1880005E546 /* HomeCollectionViewCell.swift */; }; @@ -45,6 +36,9 @@ D6E24373278BE2B80005E546 /* read.css in Resources */ = {isa = PBXBuildFile; fileRef = D6E24372278BE2B80005E546 /* read.css */; }; D6EB531D278C89C300AD2E61 /* AppNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6EB531C278C89C300AD2E61 /* AppNavigationController.swift */; }; D6EB531F278E4A7500AD2E61 /* StretchyMenuInteraction.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6EB531E278E4A7500AD2E61 /* StretchyMenuInteraction.swift */; }; + D6EEDE87285FA75D009F854E /* Fervor in Frameworks */ = {isa = PBXBuildFile; productRef = D6EEDE86285FA75D009F854E /* Fervor */; }; + D6EEDE89285FA75F009F854E /* Persistence in Frameworks */ = {isa = PBXBuildFile; productRef = D6EEDE88285FA75F009F854E /* Persistence */; }; + D6EEDE8B285FA7FD009F854E /* LocalData+Migration.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6EEDE8A285FA7FD009F854E /* LocalData+Migration.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -99,7 +93,6 @@ D608238C27DE729E00D7D5F9 /* ItemListType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemListType.swift; sourceTree = ""; }; D65B18B12750469D004A9448 /* LoginViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginViewController.swift; sourceTree = ""; }; D65B18B527504920004A9448 /* FervorController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FervorController.swift; sourceTree = ""; }; - D65B18BD275051A1004A9448 /* LocalData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalData.swift; sourceTree = ""; }; D65B18C027505348004A9448 /* HomeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeViewController.swift; sourceTree = ""; }; D68408E727947D0800E327D2 /* PrefsSceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrefsSceneDelegate.swift; sourceTree = ""; }; D68408EC2794803D00E327D2 /* PrefsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrefsView.swift; sourceTree = ""; }; @@ -113,12 +106,11 @@ D68B303E27923C0000E8B3FA /* Reader.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Reader.entitlements; sourceTree = ""; }; D68B303F2792729A00E8B3FA /* AppSplitViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSplitViewController.swift; sourceTree = ""; }; D68B304127932ED500E8B3FA /* UserActivities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserActivities.swift; sourceTree = ""; }; - D6A8A33327766C2800CCEC72 /* PersistentContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PersistentContainer.swift; sourceTree = ""; }; D6AB5E9A285F6FE100157F2F /* Fervor */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = Fervor; sourceTree = ""; }; + D6AB5E9B285F706F00157F2F /* Persistence */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = Persistence; sourceTree = ""; }; D6C687E8272CD27600874C10 /* Reader.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Reader.app; sourceTree = BUILT_PRODUCTS_DIR; }; D6C687EB272CD27600874C10 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; D6C687ED272CD27600874C10 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; - D6C687F5272CD27600874C10 /* Reader.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Reader.xcdatamodel; sourceTree = ""; }; D6C687F7272CD27700874C10 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; D6C687FA272CD27700874C10 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; D6C687FC272CD27700874C10 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -128,12 +120,6 @@ D6C6880F272CD27700874C10 /* ReaderUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReaderUITests.swift; sourceTree = ""; }; D6C68811272CD27700874C10 /* ReaderUITestsLaunchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReaderUITestsLaunchTests.swift; sourceTree = ""; }; D6E2434B278B456A0005E546 /* ItemsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemsViewController.swift; sourceTree = ""; }; - D6E24355278B96E40005E546 /* Feed+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Feed+CoreDataClass.swift"; sourceTree = ""; }; - D6E24356278B96E40005E546 /* Feed+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Feed+CoreDataProperties.swift"; sourceTree = ""; }; - D6E24359278B97240005E546 /* Item+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Item+CoreDataClass.swift"; sourceTree = ""; }; - D6E2435A278B97240005E546 /* Item+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Item+CoreDataProperties.swift"; sourceTree = ""; }; - D6E2435B278B97240005E546 /* Group+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Group+CoreDataClass.swift"; sourceTree = ""; }; - D6E2435C278B97240005E546 /* Group+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Group+CoreDataProperties.swift"; sourceTree = ""; }; D6E24361278BA1410005E546 /* ItemCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ItemCollectionViewCell.swift; sourceTree = ""; }; D6E24368278BABB40005E546 /* UIColor+App.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIColor+App.swift"; sourceTree = ""; }; D6E2436A278BB1880005E546 /* HomeCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeCollectionViewCell.swift; sourceTree = ""; }; @@ -141,6 +127,7 @@ D6E24372278BE2B80005E546 /* read.css */ = {isa = PBXFileReference; lastKnownFileType = text.css; path = read.css; sourceTree = ""; }; D6EB531C278C89C300AD2E61 /* AppNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppNavigationController.swift; sourceTree = ""; }; D6EB531E278E4A7500AD2E61 /* StretchyMenuInteraction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StretchyMenuInteraction.swift; sourceTree = ""; }; + D6EEDE8A285FA7FD009F854E /* LocalData+Migration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "LocalData+Migration.swift"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -155,6 +142,8 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + D6EEDE89285FA75F009F854E /* Persistence in Frameworks */, + D6EEDE87285FA75D009F854E /* Fervor in Frameworks */, D6E24371278BE1250005E546 /* HTMLEntities in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -231,24 +220,10 @@ name = Frameworks; sourceTree = ""; }; - D6A8A33527766E9300CCEC72 /* CoreData */ = { - isa = PBXGroup; - children = ( - D6C687F4272CD27600874C10 /* Reader.xcdatamodeld */, - D6A8A33327766C2800CCEC72 /* PersistentContainer.swift */, - D6E24355278B96E40005E546 /* Feed+CoreDataClass.swift */, - D6E24356278B96E40005E546 /* Feed+CoreDataProperties.swift */, - D6E2435B278B97240005E546 /* Group+CoreDataClass.swift */, - D6E2435C278B97240005E546 /* Group+CoreDataProperties.swift */, - D6E24359278B97240005E546 /* Item+CoreDataClass.swift */, - D6E2435A278B97240005E546 /* Item+CoreDataProperties.swift */, - ); - path = CoreData; - sourceTree = ""; - }; D6C687DF272CD27600874C10 = { isa = PBXGroup; children = ( + D6AB5E9B285F706F00157F2F /* Persistence */, D6AB5E9A285F6FE100157F2F /* Fervor */, D6C687EA272CD27600874C10 /* Reader */, D6C68804272CD27700874C10 /* ReaderTests */, @@ -279,14 +254,13 @@ D6C687ED272CD27600874C10 /* SceneDelegate.swift */, D68408E727947D0800E327D2 /* PrefsSceneDelegate.swift */, D65B18B527504920004A9448 /* FervorController.swift */, - D65B18BD275051A1004A9448 /* LocalData.swift */, D6E24368278BABB40005E546 /* UIColor+App.swift */, D6EB531E278E4A7500AD2E61 /* StretchyMenuInteraction.swift */, D68B303527907D9200E8B3FA /* ExcerptGenerator.swift */, D68B304127932ED500E8B3FA /* UserActivities.swift */, D68408EE2794808E00E327D2 /* Preferences.swift */, D608238C27DE729E00D7D5F9 /* ItemListType.swift */, - D6A8A33527766E9300CCEC72 /* CoreData */, + D6EEDE8A285FA7FD009F854E /* LocalData+Migration.swift */, D65B18AF2750468B004A9448 /* Screens */, D6C687F7272CD27700874C10 /* Assets.xcassets */, D6C687F9272CD27700874C10 /* LaunchScreen.storyboard */, @@ -370,6 +344,8 @@ name = Reader; packageProductDependencies = ( D6E24370278BE1250005E546 /* HTMLEntities */, + D6EEDE86285FA75D009F854E /* Fervor */, + D6EEDE88285FA75F009F854E /* Persistence */, ); productName = Reader; productReference = D6C687E8272CD27600874C10 /* Reader.app */; @@ -532,26 +508,18 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - D6A8A33427766C2800CCEC72 /* PersistentContainer.swift in Sources */, - D6E24357278B96E40005E546 /* Feed+CoreDataClass.swift in Sources */, D608238D27DE729E00D7D5F9 /* ItemListType.swift in Sources */, D65B18B627504920004A9448 /* FervorController.swift in Sources */, D68B304227932ED500E8B3FA /* UserActivities.swift in Sources */, D6C687EC272CD27600874C10 /* AppDelegate.swift in Sources */, - D6C687F6272CD27600874C10 /* Reader.xcdatamodeld in Sources */, D6E2436B278BB1880005E546 /* HomeCollectionViewCell.swift in Sources */, - D6E2435F278B97240005E546 /* Group+CoreDataClass.swift in Sources */, D6E24369278BABB40005E546 /* UIColor+App.swift in Sources */, - D6E2435D278B97240005E546 /* Item+CoreDataClass.swift in Sources */, + D6EEDE8B285FA7FD009F854E /* LocalData+Migration.swift in Sources */, D68408E827947D0800E327D2 /* PrefsSceneDelegate.swift in Sources */, D6EB531D278C89C300AD2E61 /* AppNavigationController.swift in Sources */, - D6E24360278B97240005E546 /* Group+CoreDataProperties.swift in Sources */, D6E2434C278B456A0005E546 /* ItemsViewController.swift in Sources */, - D6E2435E278B97240005E546 /* Item+CoreDataProperties.swift in Sources */, D6EB531F278E4A7500AD2E61 /* StretchyMenuInteraction.swift in Sources */, - D6E24358278B96E40005E546 /* Feed+CoreDataProperties.swift in Sources */, D68B30402792729A00E8B3FA /* AppSplitViewController.swift in Sources */, - D65B18BE275051A1004A9448 /* LocalData.swift in Sources */, D65B18B22750469D004A9448 /* LoginViewController.swift in Sources */, D68408ED2794803D00E327D2 /* PrefsView.swift in Sources */, D68408EF2794808E00E327D2 /* Preferences.swift in Sources */, @@ -1021,20 +989,15 @@ package = D6E2436F278BE1250005E546 /* XCRemoteSwiftPackageReference "swift-html-entities" */; productName = HTMLEntities; }; -/* End XCSwiftPackageProductDependency section */ - -/* Begin XCVersionGroup section */ - D6C687F4272CD27600874C10 /* Reader.xcdatamodeld */ = { - isa = XCVersionGroup; - children = ( - D6C687F5272CD27600874C10 /* Reader.xcdatamodel */, - ); - currentVersion = D6C687F5272CD27600874C10 /* Reader.xcdatamodel */; - path = Reader.xcdatamodeld; - sourceTree = ""; - versionGroupType = wrapper.xcdatamodel; + D6EEDE86285FA75D009F854E /* Fervor */ = { + isa = XCSwiftPackageProductDependency; + productName = Fervor; }; -/* End XCVersionGroup section */ + D6EEDE88285FA75F009F854E /* Persistence */ = { + isa = XCSwiftPackageProductDependency; + productName = Persistence; + }; +/* End XCSwiftPackageProductDependency section */ }; rootObject = D6C687E0272CD27600874C10 /* Project object */; } diff --git a/Reader/AppDelegate.swift b/Reader/AppDelegate.swift index b120f82..b419c9f 100644 --- a/Reader/AppDelegate.swift +++ b/Reader/AppDelegate.swift @@ -9,6 +9,7 @@ import UIKit import WebKit import OSLog import Combine +import Persistence @main class AppDelegate: UIResponder, UIApplicationDelegate { diff --git a/Reader/ExcerptGenerator.swift b/Reader/ExcerptGenerator.swift index 2f0dfe4..a6273b7 100644 --- a/Reader/ExcerptGenerator.swift +++ b/Reader/ExcerptGenerator.swift @@ -8,6 +8,7 @@ import Foundation import OSLog import CoreData +import Persistence // public so that it can be imported in ReaderTests even when Reader is compiled in release mode (w/ testing disabled) public struct ExcerptGenerator { diff --git a/Reader/FervorController.swift b/Reader/FervorController.swift index 9be01ff..a1e6d16 100644 --- a/Reader/FervorController.swift +++ b/Reader/FervorController.swift @@ -9,6 +9,7 @@ import Foundation import Fervor import OSLog import Combine +import Persistence actor FervorController { @@ -42,8 +43,6 @@ actor FervorController { } else { self.persistentContainer = nil } - - persistentContainer?.fervorController = self } convenience init(account: LocalData.Account) async { @@ -85,7 +84,7 @@ actor FervorController { let lastSync = try await persistentContainer.lastSyncDate() logger.info("Syncing items with last sync date: \(String(describing: lastSync), privacy: .public)") let update = try await client.syncItems(lastSync: lastSync) - try await persistentContainer.syncItems(update, setSyncState: setSyncState(_:)) + try await persistentContainer.syncItems(update, setProgress: { count, total in self.setSyncState(.updateItems(current: count, total: total)) }) try await persistentContainer.updateLastSyncDate(update.syncTimestamp) setSyncState(.excerpts) @@ -137,7 +136,7 @@ actor FervorController { } @MainActor - func markItem(_ item: Item, read: Bool) async { + func markItem(_ item: Persistence.Item, read: Bool) async { item.read = read do { let f = read ? client.read(item:) : client.unread(item:) @@ -156,11 +155,11 @@ actor FervorController { } @MainActor - func fetchItem(id: String) async throws -> Item? { + func fetchItem(id: String) async throws -> Persistence.Item? { guard let serverItem = try await client.item(id: id) else { return nil } - let item = Item(context: persistentContainer.viewContext) + let item = Persistence.Item(context: persistentContainer.viewContext) item.updateFromServer(serverItem) try persistentContainer.saveViewContext() return item diff --git a/Reader/ItemListType.swift b/Reader/ItemListType.swift index 1ffb8e8..3bef529 100644 --- a/Reader/ItemListType.swift +++ b/Reader/ItemListType.swift @@ -7,6 +7,7 @@ import Foundation import CoreData +import Persistence enum ItemListType: Hashable { case unread @@ -43,8 +44,8 @@ enum ItemListType: Hashable { return req } - var countFetchRequest: NSFetchRequest? { - let req = Reader.Item.fetchRequest() + var countFetchRequest: NSFetchRequest? { + let req = Item.fetchRequest() switch self { case .unread: req.predicate = NSPredicate(format: "read = NO") diff --git a/Reader/LocalData+Migration.swift b/Reader/LocalData+Migration.swift new file mode 100644 index 0000000..6a69282 --- /dev/null +++ b/Reader/LocalData+Migration.swift @@ -0,0 +1,23 @@ +// +// LocalData+Migration.swift +// Reader +// +// Created by Shadowfacts on 6/19/22. +// + +import Foundation +import Persistence + +extension LocalData { + static func migrateIfNecessary() { + if let accounts = UserDefaults.standard.object(forKey: "accounts") { + UserDefaults.standard.removeObject(forKey: "accounts") + defaults.set(accounts, forKey: "accounts") + } + if let mostRecentAccountID = UserDefaults.standard.object(forKey: "mostRecentAccountID") { + UserDefaults.standard.removeObject(forKey: "mostRecentAccountID") + defaults.set(mostRecentAccountID, forKey: "mostRecentAccountID") + } + } + +} diff --git a/Reader/SceneDelegate.swift b/Reader/SceneDelegate.swift index 172f623..e77a04b 100644 --- a/Reader/SceneDelegate.swift +++ b/Reader/SceneDelegate.swift @@ -8,6 +8,7 @@ import Foundation import UIKit import OSLog +import Persistence class SceneDelegate: UIResponder, UIWindowSceneDelegate { diff --git a/Reader/Screens/AppSplitViewController.swift b/Reader/Screens/AppSplitViewController.swift index aa2d71c..eb3bb9d 100644 --- a/Reader/Screens/AppSplitViewController.swift +++ b/Reader/Screens/AppSplitViewController.swift @@ -9,6 +9,7 @@ import UIKit #if targetEnvironment(macCatalyst) import AppKit #endif +import Persistence class AppSplitViewController: UISplitViewController { diff --git a/Reader/Screens/Home/HomeCollectionViewCell.swift b/Reader/Screens/Home/HomeCollectionViewCell.swift index a61682f..8a1d05b 100644 --- a/Reader/Screens/Home/HomeCollectionViewCell.swift +++ b/Reader/Screens/Home/HomeCollectionViewCell.swift @@ -6,6 +6,7 @@ // import UIKit +import Fervor class HomeCollectionViewCell: UICollectionViewListCell { diff --git a/Reader/Screens/Home/HomeViewController.swift b/Reader/Screens/Home/HomeViewController.swift index 9301873..4840781 100644 --- a/Reader/Screens/Home/HomeViewController.swift +++ b/Reader/Screens/Home/HomeViewController.swift @@ -8,6 +8,7 @@ import UIKit import CoreData import Combine +import Persistence protocol HomeViewControllerDelegate: AnyObject { func switchToAccount(_ account: LocalData.Account) diff --git a/Reader/Screens/Items/ItemCollectionViewCell.swift b/Reader/Screens/Items/ItemCollectionViewCell.swift index ac7bcde..f7ff8f9 100644 --- a/Reader/Screens/Items/ItemCollectionViewCell.swift +++ b/Reader/Screens/Items/ItemCollectionViewCell.swift @@ -6,6 +6,7 @@ // import UIKit +import Persistence protocol ItemCollectionViewCellDelegate: AnyObject { var fervorController: FervorController { get } diff --git a/Reader/Screens/Items/ItemsViewController.swift b/Reader/Screens/Items/ItemsViewController.swift index d7f77b0..576d419 100644 --- a/Reader/Screens/Items/ItemsViewController.swift +++ b/Reader/Screens/Items/ItemsViewController.swift @@ -8,6 +8,7 @@ import UIKit import CoreData import SafariServices +import Persistence protocol ItemsViewControllerDelegate: AnyObject { func showReadItem(_ item: Item) diff --git a/Reader/Screens/Read/ReadViewController.swift b/Reader/Screens/Read/ReadViewController.swift index 1b611e6..a7133cc 100644 --- a/Reader/Screens/Read/ReadViewController.swift +++ b/Reader/Screens/Read/ReadViewController.swift @@ -10,6 +10,7 @@ import WebKit import HTMLEntities import SafariServices import Combine +import Persistence class ReadViewController: UIViewController { diff --git a/Reader/UserActivities.swift b/Reader/UserActivities.swift index c16975a..fab837b 100644 --- a/Reader/UserActivities.swift +++ b/Reader/UserActivities.swift @@ -6,6 +6,7 @@ // import Foundation +import Persistence extension NSUserActivity {