Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
ce71dcd
Extract definition for `Bundle` where DB lives
mokagio Mar 7, 2025
ea82feb
App crashes – Move `WordPress.xcdatamodeld` to WordPressData
mokagio Mar 7, 2025
54d3150
App crashes – Move `BloggingPrompt` to WordPressData
mokagio Mar 7, 2025
e1eb0b3
Hardcode module name in `BloggingPrompt` model – Fixes the crash?!
mokagio Mar 7, 2025
43bb76a
Move `NSManagedObject+Lookup` to WordPressData
mokagio Mar 11, 2025
038d840
Add CocoaLumberjack to WordPressData as a dependency
mokagio Mar 11, 2025
f592cc1
Make `AsyncOperation` and `AsyncBlockOperation` `public`
mokagio Mar 11, 2025
916a7c6
Move `AsyncOperation` and `AsyncBlockOperation` to WordPressShared
mokagio Mar 11, 2025
6cc4f5b
Use Swift 5 in WordPressData to avoid having to support `Sendable` ATM
mokagio Mar 11, 2025
3b54959
WIP – Move ContextManager and all it needs to compile to WordPressData
mokagio Mar 11, 2025
029f0e0
Swift 5 in WordPressData tests
mokagio Mar 11, 2025
78608b4
Replace a couple of `sharedInstance()` calls to `shared`
mokagio Mar 11, 2025
f73d016
Fix `overrideInstance` testing property concurrency issue
mokagio Mar 11, 2025
0753eea
Make WordPressDataObj depend on CocoaLumberJack and WordPressShared
mokagio Mar 11, 2025
075560a
WIP - Move various ObjC model - BUT NOTICE THE COMMENTED OUT CODE!!!
mokagio Mar 11, 2025
48f0802
WIP - Move more files - Build is broken
mokagio Mar 11, 2025
7275ae3
Make `entityName()` open to see if compilation error goes away...
mokagio Mar 11, 2025
b0517a3
Delete leftover versions of some of the files moved
mokagio Mar 11, 2025
52fd530
Merge remote-tracking branch 'origin/trunk' into mokagio/move-xcdatam…
mokagio Mar 12, 2025
fb16306
Add `import UIKit` to some files shared with notification extension
mokagio Mar 12, 2025
8959db1
Extract SFHFKeychainUtils in a dedicated Swift package
mokagio Mar 12, 2025
b86a99c
Remove duplicated `CoreDataStackMock` class
mokagio Mar 12, 2025
2f70b94
Declare a `sharedInstance` in `CoreDataStack`
mokagio Mar 11, 2025
53f2e19
Update `.editorconfig` to indent Swift with 4 spaces outside Xcode
mokagio Mar 5, 2025
71059b1
Hack code in `Blog.m` to attempt compilation
mokagio Mar 12, 2025
2421b89
Move even more files — App still does not compile
mokagio Mar 12, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -1,9 +1,23 @@
root = true

# Apply to all files
[*]
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
charset = utf-8
trim_trailing_whitespace = true

[*.md]
trim_trailing_whitespace = false

[*.swift]
indent_size = 4

[{*.h,*.m}]
indent_size = 4

# Ruby specific rules
[{*.rb,Fastfile,Gemfile}]
indent_style = space
Expand Down
33 changes: 30 additions & 3 deletions Modules/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ let package = Package(
.library(name: "AsyncImageKit", targets: ["AsyncImageKit"]),
.library(name: "DesignSystem", targets: ["DesignSystem"]),
.library(name: "JetpackStatsWidgetsCore", targets: ["JetpackStatsWidgetsCore"]),
.library(name: "SFHFKeychainUtils", targets: ["SFHFKeychainUtils"]),
.library(name: "WordPressData", targets: ["WordPressData"]),
.library(name: "WordPressFlux", targets: ["WordPressFlux"]),
.library(name: "WordPressShared", targets: ["WordPressShared"]),
Expand Down Expand Up @@ -60,17 +61,34 @@ let package = Package(
]),
.target(name: "DesignSystem", swiftSettings: [.swiftLanguageMode(.v5)]),
.target(name: "JetpackStatsWidgetsCore", swiftSettings: [.swiftLanguageMode(.v5)]),
// SFHFKeychainUtils is an old Objective-C keychain wrapper.
// The implementatoin predates ARC, hence the dedicated target with ARC disabled, for the time being.
.target(
name: "SFHFKeychainUtils",
cSettings: [.unsafeFlags(["-fno-objc-arc"])]
),
.target(name: "UITestsFoundation", dependencies: [
.product(name: "ScreenObject", package: "ScreenObject"),
.product(name: "XCUITestHelpers", package: "ScreenObject"),
], swiftSettings: [.swiftLanguageMode(.v5)]),
.target(name: "WordPressDataObjC"),
.target(
name: "WordPressDataObjC",
dependencies: [
"SFHFKeychainUtils",
.target(name: "WordPressShared"),
.product(name: "CocoaLumberjack", package: "CocoaLumberjack"),
]
),
.target(
name: "WordPressData",
dependencies: [
.target(name: "WordPressDataObjC"),
.target(name: "WordPressSharedObjC")
]
.target(name: "WordPressSharedObjC"),
.product(name: "CocoaLumberjack", package: "CocoaLumberjack"),
.product(name: "CocoaLumberjackSwift", package: "CocoaLumberjack")
],
// Avoid supporting Sendable and concurrency for the time being...
swiftSettings: [.swiftLanguageMode(.v5)]
),
.target(name: "WordPressFlux", swiftSettings: [.swiftLanguageMode(.v5)]),
.target(name: "WordPressCore", dependencies: [.target(name: "WordPressShared"), .product(name: "WordPressAPI", package: "wordpress-rs")]),
Expand All @@ -90,6 +108,11 @@ let package = Package(
),
.testTarget(name: "JetpackStatsWidgetsCoreTests", dependencies: [.target(name: "JetpackStatsWidgetsCore")], swiftSettings: [.swiftLanguageMode(.v5)]),
.testTarget(name: "DesignSystemTests", dependencies: [.target(name: "DesignSystem")], swiftSettings: [.swiftLanguageMode(.v5)]),
.testTarget(
name: "WordPressDataTests",
dependencies: ["WordPressData"],
swiftSettings: [.swiftLanguageMode(.v5)]
),
.testTarget(name: "WordPressFluxTests", dependencies: ["WordPressFlux"], swiftSettings: [.swiftLanguageMode(.v5)]),
.testTarget(name: "AsyncImageKitTests", dependencies: [
.target(name: "AsyncImageKit"),
Expand Down Expand Up @@ -146,6 +169,7 @@ enum XcodeSupport {
]

let shareAndDraftExtensionsDependencies: [Target.Dependency] = [
"SFHFKeychainUtils",
"WordPressShared",
"WordPressUI",
.product(name: "CocoaLumberjackSwift", package: "CocoaLumberjack"),
Expand All @@ -168,6 +192,7 @@ enum XcodeSupport {
.xcodeTarget("XcodeTarget_App", dependencies: [
"DesignSystem",
"JetpackStatsWidgetsCore",
"SFHFKeychainUtils",
"WordPressData",
"WordPressFlux",
"WordPressShared",
Expand Down Expand Up @@ -206,6 +231,7 @@ enum XcodeSupport {
.product(name: "WordPressEditor", package: "AztecEditor-iOS"),
]),
.xcodeTarget("XcodeTarget_WordPressTests", dependencies: testDependencies + [
"SFHFKeychainUtils",
"WordPressShared",
.product(name: "Gravatar", package: "Gravatar-SDK-iOS"),
.product(name: "Nimble", package: "Nimble"),
Expand All @@ -216,6 +242,7 @@ enum XcodeSupport {
.xcodeTarget("XcodeTarget_ShareExtension", dependencies: shareAndDraftExtensionsDependencies),
.xcodeTarget("XcodeTarget_DraftActionExtension", dependencies: shareAndDraftExtensionsDependencies),
.xcodeTarget("XcodeTarget_NotificationServiceExtension", dependencies: [
"SFHFKeychainUtils",
"WordPressShared",
]),
.xcodeTarget("XcodeTarget_StatsWidget", dependencies: [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
// OTHER DEALINGS IN THE SOFTWARE.
//

#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>


@interface SFHFKeychainUtils : NSObject {
Expand Down Expand Up @@ -68,4 +68,4 @@
+ (NSArray<NSDictionary<NSString *, NSString *> *> *)getAllPasswordsForAccessGroup:(NSString *)accessGroup
error:(NSError **)error;

@end
@end
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import CoreData
import CocoaLumberjackSwift

// Imported from CoreData.CoreDataErrors
private let coreDataKnownErrorCodes = [
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import CocoaLumberjackSwift
import CoreData
import Foundation
import WordPressData
Expand Down Expand Up @@ -57,13 +58,18 @@ public class ContextManager: NSObject, CoreDataStack, CoreDataStackSwift {
super.init()

mainContext.automaticallyMergesChangesFromParent = true
mainContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
NullBlogPropertySanitizer(context: mainContext).sanitize()
// FIXME: Temporary disabled to move on with migration. Gives error:
// Reference to var 'NSMergeByPropertyObjectTrumpMergePolicy' is not concurrency-safe because it involves shared mutable state
// mainContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
// FIXME: We still need to migrate this. Too many dependencies on the models
// NullBlogPropertySanitizer(context: mainContext).sanitize()
}

public func newDerivedContext() -> NSManagedObjectContext {
let context = persistentContainer.newBackgroundContext()
context.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
// FIXME: Temporary disabled to move on with migration. Gives error:
// Reference to var 'NSMergeByPropertyObjectTrumpMergePolicy' is not concurrency-safe because it involves shared mutable state
// context.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
return context
}

Expand Down Expand Up @@ -155,11 +161,7 @@ public class ContextManager: NSObject, CoreDataStack, CoreDataStackSwift {

DDLogWarn("Migration required for persistent store.")

guard let modelFileURL = Bundle.main.url(forResource: "WordPress", withExtension: "momd") else {
fatalError("Can't find WordPress.momd")
}

guard let versionInfo = NSDictionary(contentsOf: modelFileURL.appendingPathComponent("VersionInfo.plist")) else {
guard let versionInfo = NSDictionary(contentsOf: WordPressData.modelURL.appendingPathComponent("VersionInfo.plist")) else {
fatalError("Can't get the object model's version info")
}

Expand Down Expand Up @@ -222,9 +224,7 @@ private extension ContextManager {

private extension ContextManager {
static func createPersistentContainer(storeURL: URL, modelName: String) -> NSPersistentContainer {
guard var modelFileURL = Bundle.main.url(forResource: "WordPress", withExtension: "momd") else {
fatalError("Can't find WordPress.momd")
}
var modelFileURL = WordPressData.modelURL

if modelName != ContextManagerModelNameCurrent {
modelFileURL = modelFileURL.appendingPathComponent(modelName).appendingPathExtension("mom")
Expand All @@ -234,13 +234,15 @@ private extension ContextManager {
fatalError("Can't create object model named \(modelName) at \(modelFileURL)")
}

let startupEvent = SentryStartupEvent()
// FIXME: Discarding Sentry in the context of this migration 1/5
// let startupEvent = SentryStartupEvent()

do {
try migrateDataModelsIfNecessary(storeURL: storeURL, objectModel: objectModel)
} catch {
DDLogError("Unable to migrate store: \(error)")
startupEvent.add(error: error as NSError)
// FIXME: Discarding Sentry in the context of this migration 2/5
// startupEvent.add(error: error as NSError)
}

let storeDescription = NSPersistentStoreDescription(url: storeURL)
Expand All @@ -254,16 +256,19 @@ private extension ContextManager {
}

DDLogError("Error opening the database. \(error)\nDeleting the file and trying again")
startupEvent.add(error: error)
// FIXME: Discarding Sentry in the context of this migration 3/5
// startupEvent.add(error: error)

// make a backup of the old database
do {
try CoreDataIterativeMigrator.backupDatabase(at: storeURL)
} catch {
startupEvent.add(error: error)
// FIXME: Discarding Sentry in the context of this migration 4/5
// startupEvent.add(error: error)
}

startupEvent.send(title: "Can't initialize Core Data stack")
// FIXME: Discarding Sentry in the context of this migration 5/5
// startupEvent.send(title: "Can't initialize Core Data stack")
objc_exception_throw(
NSException(
name: NSExceptionName(rawValue: "Can't initialize Core Data stack"),
Expand All @@ -279,19 +284,20 @@ private extension ContextManager {

extension ContextManager {
private static let internalSharedInstance = ContextManager()

/// Tests purpose only
static var overrideInstance: ContextManager?
nonisolated(unsafe) static var overrideInstance: ContextManager?

@objc class func sharedInstance() -> ContextManager {
@objc public class func sharedInstance() -> CoreDataStack {
if let overrideInstance {
return overrideInstance
}

return ContextManager.internalSharedInstance
}

static var shared: ContextManager {
return sharedInstance()
static var shared: CoreDataStackSwift {
return sharedInstance() as! CoreDataStackSwift
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import Foundation
import CoreData
import CocoaLumberjackSwift
import Foundation

/// CoreDataIterativeMigrator: Migrates through a series of models to allow for users to skip app versions without risk.
///
Expand Down Expand Up @@ -271,7 +272,7 @@ private extension CoreDataIterativeMigrator {

static func models(for names: [String]) throws -> [NSManagedObjectModel] {
let models = try names.map { (name) -> NSManagedObjectModel in
guard let url = urlForModel(name: name, in: nil),
guard let url = WordPressData.urlForModel(name: name, in: nil),
let model = NSManagedObjectModel(contentsOf: url) else {
let description = "No model found for \(name)"
throw error(with: .noModelFound, description: description)
Expand All @@ -282,25 +283,6 @@ private extension CoreDataIterativeMigrator {

return models
}

static func urlForModel(name: String, in directory: String?) -> URL? {
let bundle = Bundle(for: ContextManager.self)
var url = bundle.url(forResource: name, withExtension: "mom", subdirectory: directory)

if url != nil {
return url
}

let momdPaths = bundle.paths(forResourcesOfType: "momd", inDirectory: directory)
momdPaths.forEach { (path) in
if url != nil {
return
}
url = bundle.url(forResource: name, withExtension: "mom", subdirectory: URL(fileURLWithPath: path).lastPathComponent)
}

return url
}
}

enum IterativeMigratorErrorCodes: Int {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import Foundation
import CoreData
import CocoaLumberjackSwift
import WordPressShared

// MARK: - NSManagedObject Default entityName Helper
Expand All @@ -11,7 +12,7 @@ extension NSManagedObject {
///
/// Note: entity().name returns nil as per iOS 10, in Unit Testing Targets. Awesome.
///
@objc class func entityName() -> String {
@objc open class func entityName() -> String {
return entity().name ?? classNameWithoutNamespaces()
}

Expand Down Expand Up @@ -88,7 +89,8 @@ extension NSManagedObjectContext {
///
/// - Parameter predicate: Defines the conditions that any given object should meet.
///
func firstObject<T: NSManagedObject>(ofType type: T.Type, matching predicate: NSPredicate) -> T? {
// FIXME: Might be able to return internal after Role is migrated
public func firstObject<T: NSManagedObject>(ofType type: T.Type, matching predicate: NSPredicate) -> T? {
let request = T.safeFetchRequest()
request.predicate = predicate
request.fetchLimit = 1
Expand Down Expand Up @@ -140,7 +142,8 @@ extension NSManagedObjectContext {
objects = try fetch(request) as? [T]
} catch {
DDLogError("Error loading Objects [\(String(describing: T.entityName))")
wpAssertionFailure("CoreData.loadObjects failed", userInfo: ["error": "\(error)"])
// FIXME: Enabling this will require adding dependencies such as Tracks through the chain...
// wpAssertionFailure("CoreData.loadObjects failed", userInfo: ["error": "\(error)"])
}

return objects ?? []
Expand Down Expand Up @@ -191,7 +194,7 @@ extension ContextManager.ContextManagerError: LocalizedError, CustomDebugStringC
}
}

extension CoreDataStack {
public extension CoreDataStack {
/// Perform a query using the `mainContext` and return the result.
///
/// - Warning: Do not return `NSManagedObject` instances from the closure.
Expand Down Expand Up @@ -357,8 +360,7 @@ extension CoreDataStack {
}

private func migrateDatabaseIfNecessary(at databaseLocation: URL) throws {
guard let modelFileURL = Bundle.main.url(forResource: "WordPress", withExtension: "momd"),
let objectModel = NSManagedObjectModel(contentsOf: modelFileURL) else {
guard let objectModel = NSManagedObjectModel(contentsOf: WordPressData.modelURL) else {
return
}
try ContextManager.migrateDataModelsIfNecessary(storeURL: databaseLocation, objectModel: objectModel)
Expand Down
45 changes: 45 additions & 0 deletions Modules/Sources/WordPressData/Models/BloggingPrompt.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import CoreData
import WordPressSharedObjC

public class BloggingPrompt: NSManagedObject {

/// The unique ID for the prompt, received from the server.
@NSManaged public var promptID: Int32

/// The site ID for the prompt.
@NSManaged public var siteID: Int32

/// The prompt content to be displayed at entry points.
@NSManaged public var text: String

/// The attribution source for the prompt.
@NSManaged public var attribution: String

/// The prompt date. Time information should be ignored.
@NSManaged public var date: Date

/// Whether the current user has answered the prompt in `siteID`.
@NSManaged public var answered: Bool

/// The number of users that has answered the prompt.
@NSManaged public var answerCount: Int32

/// Contains avatar URLs of some users that have answered the prompt.
@NSManaged public var displayAvatarURLs: [URL]

/// Contains additional tags that should be appended to the post for this prompt's answer.
@NSManaged public var additionalPostTags: [String]?

@nonobjc public class func fetchRequest() -> NSFetchRequest<BloggingPrompt> {
return NSFetchRequest<BloggingPrompt>(entityName: Self.classNameWithoutNamespaces())
}

@nonobjc public class func newObject(in context: NSManagedObjectContext) -> BloggingPrompt? {
return NSEntityDescription.insertNewObject(forEntityName: Self.classNameWithoutNamespaces(), into: context) as? BloggingPrompt
}

public override func awakeFromInsert() {
self.date = .init(timeIntervalSince1970: 0)
self.displayAvatarURLs = []
}
}
Loading