Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
8 changes: 8 additions & 0 deletions WordPress/Classes/Models/Role.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,14 @@ extension Role {
func toUnmanaged() -> RemoteRole {
return RemoteRole(slug: slug, name: name)
}

static func lookup(withBlogID blogID: NSManagedObjectID, slug: String, in context: NSManagedObjectContext) throws -> Role? {
guard let blog = try context.existingObject(with: blogID) as? Blog else {
return nil
}
let predicate = NSPredicate(format: "slug = %@ AND blog = %@", slug, blog)
return context.firstObject(ofType: Role.self, matching: predicate)
}
}

extension Role {
Expand Down
88 changes: 30 additions & 58 deletions WordPress/Classes/Services/ReaderSearchSuggestionService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,33 @@ import CocoaLumberjack
/// Provides functionality for fetching, saving, and deleting search phrases
/// used to search for content in the reader.
///
@objc class ReaderSearchSuggestionService: LocalCoreDataService {
@objc class ReaderSearchSuggestionService: NSObject {

private let coreDataStack: CoreDataStack

@objc init(coreDataStack: CoreDataStack) {
self.coreDataStack = coreDataStack
super.init()
}

/// Creates or updates an existing record for the specified search phrase.
///
/// - Parameters:
/// - phrase: The search phrase in question.
///
@objc func createOrUpdateSuggestionForPhrase(_ phrase: String) {
var suggestion = findSuggestionForPhrase(phrase)
if suggestion == nil {
suggestion = NSEntityDescription.insertNewObject(forEntityName: ReaderSearchSuggestion.classNameWithoutNamespaces(),
into: managedObjectContext) as? ReaderSearchSuggestion
suggestion?.searchPhrase = phrase
@objc(createOrUpdateSuggestionForPhrase:)
func createOrUpdateSuggestion(forPhrase phrase: String) {
self.coreDataStack.performAndSave { context in
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick: Is it necessary to access coreDataStack from self here?

Suggested change
self.coreDataStack.performAndSave { context in
coreDataStack.performAndSave { context in

var suggestion = self.findSuggestion(forPhrase: phrase, in: context)
if suggestion == nil {
suggestion = NSEntityDescription.insertNewObject(
forEntityName: ReaderSearchSuggestion.classNameWithoutNamespaces(),
into: context
) as? ReaderSearchSuggestion
suggestion?.searchPhrase = phrase
}
suggestion?.date = Date()
}
suggestion?.date = Date()
ContextManager.sharedInstance().save(managedObjectContext)
}


Expand All @@ -30,71 +41,32 @@ import CocoaLumberjack
///
/// - Returns: A matching search phrase or nil.
///
@objc func findSuggestionForPhrase(_ phrase: String) -> ReaderSearchSuggestion? {
private func findSuggestion(forPhrase phrase: String, in context: NSManagedObjectContext) -> ReaderSearchSuggestion? {
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "ReaderSearchSuggestion")
fetchRequest.predicate = NSPredicate(format: "searchPhrase MATCHES[cd] %@", phrase)

var suggestions = [ReaderSearchSuggestion]()
do {
suggestions = try managedObjectContext.fetch(fetchRequest) as! [ReaderSearchSuggestion]
suggestions = try context.fetch(fetchRequest) as! [ReaderSearchSuggestion]
} catch let error as NSError {
DDLogError("Error fetching search suggestion for phrase \(phrase) : \(error.localizedDescription)")
}

return suggestions.first
}


/// Finds and returns all ReaderSearchSuggestion starting with the specified search phrase.
///
/// - Parameters:
/// - phrase: The search phrase in question.
///
/// - Returns: An array of matching `ReaderSearchSuggestion`s.
///
@objc func fetchSuggestionsLikePhrase(_ phrase: String) -> [ReaderSearchSuggestion] {
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "ReaderSearchSuggestion")
fetchRequest.predicate = NSPredicate(format: "searchPhrase BEGINSWITH[cd] %@", phrase)

let sort = NSSortDescriptor(key: "date", ascending: false)
fetchRequest.sortDescriptors = [sort]

var suggestions = [ReaderSearchSuggestion]()
do {
suggestions = try managedObjectContext.fetch(fetchRequest) as! [ReaderSearchSuggestion]
} catch let error as NSError {
DDLogError("Error fetching search suggestions for phrase \(phrase) : \(error.localizedDescription)")
}

return suggestions
}


/// Deletes the specified search suggestion.
///
/// - Parameters:
/// - suggestion: The `ReaderSearchSuggestion` to delete.
///
@objc func deleteSuggestion(_ suggestion: ReaderSearchSuggestion) {
managedObjectContext.delete(suggestion)
ContextManager.sharedInstance().saveContextAndWait(managedObjectContext)
}


/// Deletes all saved search suggestions.
///
@objc func deleteAllSuggestions() {
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "ReaderSearchSuggestion")
var suggestions = [ReaderSearchSuggestion]()
do {
suggestions = try managedObjectContext.fetch(fetchRequest) as! [ReaderSearchSuggestion]
} catch let error as NSError {
DDLogError("Error fetching search suggestion : \(error.localizedDescription)")
}
for suggestion in suggestions {
managedObjectContext.delete(suggestion)
self.coreDataStack.performAndSave { context in
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "ReaderSearchSuggestion")
do {
let suggestions = try context.fetch(fetchRequest) as! [ReaderSearchSuggestion]
suggestions.forEach(context.delete(_:))
} catch let error as NSError {
DDLogError("Error fetching search suggestion : \(error.localizedDescription)")
}
}
ContextManager.sharedInstance().save(managedObjectContext)
}

}
2 changes: 1 addition & 1 deletion WordPress/Classes/Services/ReaderTopicService.m
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,7 @@ - (ReaderSearchTopic *)searchTopicForSearchPhrase:(NSString *)phrase
topic.following = NO;

// Save / update the search phrase to use it as a suggestion later.
ReaderSearchSuggestionService *suggestionService = [[ReaderSearchSuggestionService alloc] initWithManagedObjectContext:self.managedObjectContext];
ReaderSearchSuggestionService *suggestionService = [[ReaderSearchSuggestionService alloc] initWithCoreDataStack:[ContextManager sharedInstance]];
[suggestionService createOrUpdateSuggestionForPhrase:phrase];

[[ContextManager sharedInstance] saveContextAndWait:self.managedObjectContext];
Expand Down
24 changes: 8 additions & 16 deletions WordPress/Classes/Services/RoleService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,40 +6,34 @@ import WordPressKit
struct RoleService {
let blog: Blog

fileprivate let context: NSManagedObjectContext
fileprivate let coreDataStack: CoreDataStack
fileprivate let remote: PeopleServiceRemote
fileprivate let siteID: Int

init?(blog: Blog, context: NSManagedObjectContext) {
init?(blog: Blog, coreDataStack: CoreDataStack) {
guard let api = blog.wordPressComRestApi(), let dotComID = blog.dotComID as? Int else {
return nil
}

self.remote = PeopleServiceRemote(wordPressComRestApi: api)
self.siteID = dotComID
self.blog = blog
self.context = context
}

/// Returns a role from Core Data with the given slug.
///
func getRole(slug: String) -> Role? {
let predicate = NSPredicate(format: "slug = %@ AND blog = %@", slug, blog)
return context.firstObject(ofType: Role.self, matching: predicate)
self.coreDataStack = coreDataStack
}

/// Forces a refresh of roles from the api and stores them in Core Data.
///
func fetchRoles(success: @escaping ([Role]) -> Void, failure: @escaping (Error) -> Void) {
func fetchRoles(success: @escaping () -> Void, failure: @escaping (Error) -> Void) {
remote.getUserRoles(siteID, success: { (remoteRoles) in
let roles = self.mergeRoles(remoteRoles)
success(roles)
self.coreDataStack.performAndSave({ context in
self.mergeRoles(remoteRoles, in: context)
}, completion: success, on: .main)
}, failure: failure)
}
}

private extension RoleService {
func mergeRoles(_ remoteRoles: [RemoteRole]) -> [Role] {
func mergeRoles(_ remoteRoles: [RemoteRole], in context: NSManagedObjectContext) {
let existingRoles = blog.roles ?? []
var rolesToKeep = [Role]()
for (order, remoteRole) in remoteRoles.enumerated() {
Expand All @@ -57,7 +51,5 @@ private extension RoleService {
}
let rolesToDelete = existingRoles.subtracting(rolesToKeep)
rolesToDelete.forEach(context.delete(_:))
ContextManager.sharedInstance().save(context)
return rolesToKeep
}
}
28 changes: 18 additions & 10 deletions WordPress/Classes/Services/SiteManagementService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,14 @@ extension NSNotification.Name {

/// SiteManagementService handles operations for managing a WordPress.com site.
///
open class SiteManagementService: LocalCoreDataService {
open class SiteManagementService: NSObject {

private let coreDataStack: CoreDataStack

init(coreDataStack: CoreDataStack) {
self.coreDataStack = coreDataStack
}

/// Deletes the specified WordPress.com site.
///
/// - Parameters:
Expand All @@ -33,15 +40,16 @@ open class SiteManagementService: LocalCoreDataService {
}
remote.deleteSite(blog.dotComID!,
success: {
self.managedObjectContext.perform {
let blogService = BlogService(managedObjectContext: self.managedObjectContext)
blogService.remove(blog)

ContextManager.sharedInstance().save(self.managedObjectContext, completion: {
NotificationCenter.default.post(name: .WPSiteDeleted, object: nil)
success?()
}, on: .main)
}
self.coreDataStack.performAndSave({ context in
guard let blogInContext = try? context.existingObject(with: blog.objectID) as? Blog else {
return
}
let blogService = BlogService(managedObjectContext: context)
blogService.remove(blogInContext)
}, completion: {
NotificationCenter.default.post(name: .WPSiteDeleted, object: nil)
success?()
}, on: .main)
},
failure: { error in
failure?(error)
Expand Down
13 changes: 6 additions & 7 deletions WordPress/Classes/Services/SiteSegmentsService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,28 +11,27 @@ protocol SiteSegmentsService {
}

// MARK: - SiteSegmentsService
final class SiteCreationSegmentsService: LocalCoreDataService, SiteSegmentsService {
final class SiteCreationSegmentsService: SiteSegmentsService {

// MARK: Properties

/// A facade for WPCOM services.
private let remoteService: WordPressComServiceRemote

// MARK: LocalCoreDataService

override init(managedObjectContext context: NSManagedObjectContext) {
init(coreDataStack: CoreDataStack) {
let account = coreDataStack.performQuery { context in
try? WPAccount.lookupDefaultWordPressComAccount(in: context)
}

let api: WordPressComRestApi

if let account = try? WPAccount.lookupDefaultWordPressComAccount(in: context) {
if let account {
api = account.wordPressComRestV2Api
} else {
api = WordPressComRestApi.anonymousApi(userAgent: WPUserAgent.wordPress(), localeKey: WordPressComRestApi.LocaleKeyV2)
}

self.remoteService = WordPressComServiceRemote(wordPressComRestApi: api)

super.init(managedObjectContext: context)
}

// MARK: SiteSegmentsService
Expand Down
13 changes: 6 additions & 7 deletions WordPress/Classes/Services/SiteVerticalsService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -47,26 +47,25 @@ final class MockSiteVerticalsService: SiteVerticalsService {

/// Retrieves candidate Site Verticals used to create a new site.
///
final class SiteCreationVerticalsService: LocalCoreDataService, SiteVerticalsService {
final class SiteCreationVerticalsService: SiteVerticalsService {

// MARK: Properties

/// A facade for WPCOM services.
private let remoteService: WordPressComServiceRemote

// MARK: LocalCoreDataService

override init(managedObjectContext context: NSManagedObjectContext) {
init(coreDataStack: CoreDataStack) {
let account = coreDataStack.performQuery { context in
try? WPAccount.lookupDefaultWordPressComAccount(in: context)
}

let api: WordPressComRestApi
if let account = try? WPAccount.lookupDefaultWordPressComAccount(in: context) {
if let account {
api = account.wordPressComRestV2Api
} else {
api = WordPressComRestApi.anonymousApi(userAgent: WPUserAgent.wordPress(), localeKey: WordPressComRestApi.LocaleKeyV2)
}
self.remoteService = WordPressComServiceRemote(wordPressComRestApi: api)

super.init(managedObjectContext: context)
}

// MARK: SiteVerticalsService
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -510,7 +510,7 @@ private extension QuickStartTourGuide {
}

private func grantCongratulationsAward(for blog: Blog) {
let service = SiteManagementService(managedObjectContext: ContextManager.sharedInstance().mainContext)
let service = SiteManagementService(coreDataStack: ContextManager.sharedInstance())
service.markQuickStartChecklistAsComplete(for: blog)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ open class DeleteSiteViewController: UITableViewController {

let trackedBlog = blog
WPAppAnalytics.track(.siteSettingsDeleteSiteRequested, with: trackedBlog)
let service = SiteManagementService(managedObjectContext: ContextManager.sharedInstance().mainContext)
let service = SiteManagementService(coreDataStack: ContextManager.sharedInstance())
service.deleteSiteForBlog(blog,
success: { [weak self] in
WPAppAnalytics.track(.siteSettingsDeleteSiteResponseOK, with: trackedBlog)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ public extension SiteSettingsViewController {

let trackedBlog = blog
WPAppAnalytics.track(.siteSettingsExportSiteRequested, with: trackedBlog)
let service = SiteManagementService(managedObjectContext: ContextManager.sharedInstance().mainContext)
let service = SiteManagementService(coreDataStack: ContextManager.sharedInstance())
service.exportContentForBlog(blog,
success: {
WPAppAnalytics.track(.siteSettingsExportSiteResponseOK, with: trackedBlog)
Expand Down Expand Up @@ -79,7 +79,7 @@ public extension SiteSettingsViewController {
SVProgressHUD.show(withStatus: status)

WPAppAnalytics.track(.siteSettingsDeleteSitePurchasesRequested, with: blog)
let service = SiteManagementService(managedObjectContext: ContextManager.sharedInstance().mainContext)
let service = SiteManagementService(coreDataStack: ContextManager.sharedInstance())
service.getActivePurchasesForBlog(blog,
success: { [weak self] purchases in
SVProgressHUD.dismiss()
Expand Down
13 changes: 6 additions & 7 deletions WordPress/Classes/ViewRelated/People/PeopleViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ class PeopleViewController: UITableViewController, UIViewControllerRestoration {
tableView.deselectSelectedRowWithAnimation(true)
refreshNoResultsView()

guard let blog = blog else {
guard let blog else {
return
}

Expand Down Expand Up @@ -420,7 +420,7 @@ private extension PeopleViewController {
func loadUsersPage(_ offset: Int = 0, success: @escaping ((_ retrieved: Int, _ shouldLoadMore: Bool) -> Void)) {
guard let blog = blogInContext,
let peopleService = PeopleService(blog: blog, coreDataStack: ContextManager.shared),
let roleService = RoleService(blog: blog, context: viewContext) else {
let roleService = RoleService(blog: blog, coreDataStack: ContextManager.shared) else {
return
}

Expand All @@ -438,7 +438,7 @@ private extension PeopleViewController {
})

group.enter()
roleService.fetchRoles(success: {_ in
roleService.fetchRoles(success: {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💅 👍

group.leave()
}, failure: { error in
loadError = error
Expand Down Expand Up @@ -521,11 +521,10 @@ private extension PeopleViewController {
}

func role(person: Person) -> Role? {
guard let blog = blog,
let service = RoleService(blog: blog, context: viewContext) else {
return nil
guard let blog = blog else {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was going to suggest guard let blog else but I got annoyed at myself for being so pedantic about things that could be automated...

SwiftLint supports this, I should add the check to our https://github.com/Automattic/swiftlint-config and integrate it here...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No worries. It's always good to have cleaner code. Updated in 4412706

return nil
}
return service.getRole(slug: person.role)
return try? Role.lookup(withBlogID: blog.objectID, slug: person.role, in: viewContext)
}

func setupFilterBar() {
Expand Down
Loading