-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add support for X-posting in Gutenberg
- Add new Core Data model `WordPress 103` with new entity `SiteSuggestion` - Add a one-to-many relationship between `Blog` and `SiteSuggestion` - Add `XPostSuggestionService` for fetching xpost suggestions from the remote server - Add xpost support to Gutenberg in `GutenbergViewController` - Extend `SuggestionsTableView` to support xposts
- Loading branch information
Showing
17 changed files
with
1,326 additions
and
118 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
34 changes: 34 additions & 0 deletions
34
WordPress/Classes/Models/SiteSuggestion+CoreDataClass.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
import Foundation | ||
import CoreData | ||
|
||
extension CodingUserInfoKey { | ||
static let managedObjectContext = CodingUserInfoKey(rawValue: "managedObjectContext")! | ||
} | ||
|
||
enum DecoderError: Error { | ||
case missingManagedObjectContext | ||
} | ||
|
||
@objc(SiteSuggestion) | ||
public class SiteSuggestion: NSManagedObject, Decodable { | ||
enum CodingKeys: String, CodingKey { | ||
case title = "title" | ||
case siteURL = "siteurl" | ||
case subdomain = "subdomain" | ||
case blavatarURL = "blavatar" | ||
} | ||
|
||
required convenience public init(from decoder: Decoder) throws { | ||
guard let managedObjectContext = decoder.userInfo[CodingUserInfoKey.managedObjectContext] as? NSManagedObjectContext else { | ||
throw DecoderError.missingManagedObjectContext | ||
} | ||
|
||
self.init(context: managedObjectContext) | ||
|
||
let container = try decoder.container(keyedBy: CodingKeys.self) | ||
self.title = try container.decode(String.self, forKey: .title) | ||
self.siteURL = try container.decode(URL.self, forKey: .siteURL) | ||
self.subdomain = try container.decode(String.self, forKey: .subdomain) | ||
self.blavatarURL = try container.decode(URL.self, forKey: .blavatarURL) | ||
} | ||
} |
17 changes: 17 additions & 0 deletions
17
WordPress/Classes/Models/SiteSuggestion+CoreDataProperties.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import Foundation | ||
import CoreData | ||
|
||
|
||
extension SiteSuggestion { | ||
|
||
@nonobjc public class func fetchRequest() -> NSFetchRequest<SiteSuggestion> { | ||
return NSFetchRequest<SiteSuggestion>(entityName: "SiteSuggestion") | ||
} | ||
|
||
@NSManaged public var title: String? | ||
@NSManaged public var siteURL: URL? | ||
@NSManaged public var subdomain: String? | ||
@NSManaged public var blavatarURL: URL? | ||
@NSManaged public var blog: Blog? | ||
|
||
} |
108 changes: 108 additions & 0 deletions
108
WordPress/Classes/Services/XpostSuggestionService.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
import Foundation | ||
|
||
/// A service to fetch and persist a list of sites that can be used for x-posting. | ||
struct XpostSuggestionService { | ||
|
||
enum ServiceError: Error { | ||
case missingAPI | ||
case missingManagedObjectContext | ||
case hostnameNotAvailable | ||
case noResultsAvailable | ||
} | ||
|
||
static var hasRequested = false | ||
|
||
/** | ||
Fetch cached suggestions if available, otherwise from the network if the device is online. | ||
|
||
@param the blog/site to retrieve suggestions for | ||
@param completion callback containing list of suggestions, or nil if unavailable | ||
*/ | ||
static func suggestions(for blog: Blog, completion: @escaping (Result<[SiteSuggestion], Error>) -> Void) { | ||
|
||
if let results = retrievePersistedResults(for: blog), results.isEmpty == false { | ||
completion(.success(results)) | ||
} else if ReachabilityUtils.isInternetReachable() { | ||
fetchAndPersistSuggestions(for: blog, completion: completion) | ||
} else { | ||
completion(.failure(ServiceError.noResultsAvailable)) | ||
} | ||
} | ||
|
||
private static func fetchAndPersistSuggestions(for blog: Blog, completion: @escaping (Result<[SiteSuggestion], Error>) -> Void) { | ||
|
||
guard !hasRequested else { return } | ||
self.hasRequested = true | ||
|
||
guard let api = blog.wordPressComRestApi() else { | ||
completion(.failure(ServiceError.missingAPI)) | ||
return | ||
} | ||
|
||
guard let managedObjectContext = blog.managedObjectContext else { | ||
completion(.failure(ServiceError.missingManagedObjectContext)) | ||
return | ||
} | ||
|
||
guard let hostname = blog.hostname else { | ||
completion(.failure(ServiceError.hostnameNotAvailable)) | ||
return | ||
} | ||
|
||
let urlString = "/wpcom/v2/sites/\(hostname)/xposts" | ||
|
||
api.GET(urlString, parameters: nil) { responseObject, httpResponse in | ||
do { | ||
let data = try JSONSerialization.data(withJSONObject: responseObject) | ||
|
||
try self.purgeExistingResults(for: blog, using: managedObjectContext) | ||
|
||
let siteSuggestions = try self.persist(data: data, to: blog, using: managedObjectContext) | ||
completion(.success(siteSuggestions)) | ||
} catch { | ||
completion(.failure(error)) | ||
} | ||
|
||
self.hasRequested = false | ||
} failure: { error, _ in | ||
completion(.failure(error)) | ||
self.hasRequested = false | ||
} | ||
} | ||
|
||
private static func purgeExistingResults(for blog: Blog, using managedObjectContext: NSManagedObjectContext) throws { | ||
blog.siteSuggestions?.forEach { siteSuggestion in | ||
managedObjectContext.delete(siteSuggestion) | ||
} | ||
try managedObjectContext.save() | ||
} | ||
|
||
private static func persist(data: Data, to blog: Blog, using managedObjectContext: NSManagedObjectContext) throws -> [SiteSuggestion] { | ||
let decoder = JSONDecoder() | ||
decoder.userInfo[CodingUserInfoKey.managedObjectContext] = managedObjectContext | ||
let siteSuggestions = try decoder.decode([SiteSuggestion].self, from: data) | ||
blog.siteSuggestions = Set(siteSuggestions) | ||
try managedObjectContext.save() | ||
return siteSuggestions | ||
} | ||
|
||
private static func retrievePersistedResults(for blog: Blog) -> [SiteSuggestion]? { | ||
guard let results = blog.siteSuggestions else { return nil } | ||
return Array(results) | ||
} | ||
} | ||
|
||
extension XpostSuggestionService.ServiceError: CustomNSError { | ||
static var errorDomain: String { return "XpostSuggestionService.ServiceError" } | ||
|
||
var errorCode: Int { return 0 } | ||
|
||
var errorUserInfo: [String : Any] { | ||
switch self { | ||
case .missingAPI: return [NSDebugDescriptionErrorKey: "Blog hostname not available"] | ||
case .missingManagedObjectContext: return [NSDebugDescriptionErrorKey: "Managed object context not available"] | ||
case .hostnameNotAvailable: return [NSDebugDescriptionErrorKey: "Blog hostname not available"] | ||
case .noResultsAvailable: return [NSDebugDescriptionErrorKey: "The device is offline and there are no suggestions in the cache"] | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.