2021-02-01 18:46:25 +00:00
|
|
|
|
import Foundation
|
|
|
|
|
import WriteFreely
|
|
|
|
|
|
|
|
|
|
extension WriteFreelyModel {
|
|
|
|
|
func loginHandler(result: Result<WFUser, Error>) {
|
|
|
|
|
DispatchQueue.main.async {
|
|
|
|
|
self.isLoggingIn = false
|
|
|
|
|
}
|
|
|
|
|
do {
|
|
|
|
|
let user = try result.get()
|
|
|
|
|
fetchUserCollections()
|
|
|
|
|
fetchUserPosts()
|
2021-08-20 21:13:41 +00:00
|
|
|
|
do {
|
|
|
|
|
try saveTokenToKeychain(user.token, username: user.username, server: account.server)
|
|
|
|
|
DispatchQueue.main.async {
|
|
|
|
|
self.account.login(user)
|
|
|
|
|
}
|
|
|
|
|
} catch {
|
2022-07-27 13:56:32 +00:00
|
|
|
|
self.currentError = KeychainError.couldNotStoreAccessToken
|
2021-02-01 18:46:25 +00:00
|
|
|
|
}
|
|
|
|
|
} catch WFError.notFound {
|
2022-07-27 13:56:32 +00:00
|
|
|
|
self.currentError = AccountError.usernameNotFound
|
2021-02-01 18:46:25 +00:00
|
|
|
|
} catch WFError.unauthorized {
|
2022-07-27 13:56:32 +00:00
|
|
|
|
self.currentError = AccountError.invalidPassword
|
2021-02-01 18:46:25 +00:00
|
|
|
|
} catch {
|
|
|
|
|
if (error as NSError).domain == NSURLErrorDomain,
|
|
|
|
|
(error as NSError).code == -1003 {
|
2022-07-27 13:56:32 +00:00
|
|
|
|
self.currentError = AccountError.serverNotFound
|
2021-02-01 18:46:25 +00:00
|
|
|
|
} else {
|
2022-07-27 13:56:32 +00:00
|
|
|
|
self.currentError = error
|
2021-02-01 18:46:25 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func logoutHandler(result: Result<Bool, Error>) {
|
|
|
|
|
do {
|
|
|
|
|
_ = try result.get()
|
|
|
|
|
do {
|
|
|
|
|
try purgeTokenFromKeychain(username: account.user?.username, server: account.server)
|
|
|
|
|
client = nil
|
|
|
|
|
DispatchQueue.main.async {
|
|
|
|
|
self.account.logout()
|
2022-07-27 13:56:32 +00:00
|
|
|
|
do {
|
|
|
|
|
try LocalStorageManager.standard.purgeUserCollections()
|
|
|
|
|
try self.posts.purgePublishedPosts()
|
|
|
|
|
} catch {
|
|
|
|
|
self.currentError = error
|
|
|
|
|
}
|
2021-02-01 18:46:25 +00:00
|
|
|
|
}
|
|
|
|
|
} catch {
|
2022-07-27 13:56:32 +00:00
|
|
|
|
self.currentError = KeychainError.couldNotPurgeAccessToken
|
2021-02-01 18:46:25 +00:00
|
|
|
|
}
|
|
|
|
|
} catch WFError.notFound {
|
|
|
|
|
// The user token is invalid or doesn't exist, so it's been invalidated by the server. Proceed with
|
|
|
|
|
// purging the token from the Keychain, destroying the client object, and setting the AccountModel to its
|
|
|
|
|
// logged-out state.
|
|
|
|
|
do {
|
|
|
|
|
try purgeTokenFromKeychain(username: account.user?.username, server: account.server)
|
|
|
|
|
client = nil
|
|
|
|
|
DispatchQueue.main.async {
|
|
|
|
|
self.account.logout()
|
2022-07-27 13:56:32 +00:00
|
|
|
|
do {
|
|
|
|
|
try LocalStorageManager.standard.purgeUserCollections()
|
|
|
|
|
try self.posts.purgePublishedPosts()
|
|
|
|
|
} catch {
|
|
|
|
|
self.currentError = error
|
|
|
|
|
}
|
2021-02-01 18:46:25 +00:00
|
|
|
|
}
|
|
|
|
|
} catch {
|
2022-07-27 13:56:32 +00:00
|
|
|
|
self.currentError = KeychainError.couldNotPurgeAccessToken
|
2021-02-01 18:46:25 +00:00
|
|
|
|
}
|
|
|
|
|
} catch {
|
|
|
|
|
// We get a 'cannot parse response' (similar to what we were seeing in the Swift package) NSURLError here,
|
|
|
|
|
// so we're using a hacky workaround — if we get the NSURLError, but the AccountModel still thinks we're
|
|
|
|
|
// logged in, try calling the logout function again and see what we get.
|
|
|
|
|
// Conditional cast from 'Error' to 'NSError' always succeeds but is the only way to check error properties.
|
|
|
|
|
if (error as NSError).domain == NSURLErrorDomain,
|
|
|
|
|
(error as NSError).code == NSURLErrorCannotParseResponse {
|
|
|
|
|
if account.isLoggedIn {
|
|
|
|
|
self.logout()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func fetchUserCollectionsHandler(result: Result<[WFCollection], Error>) {
|
|
|
|
|
// We're done with the network request.
|
|
|
|
|
DispatchQueue.main.async {
|
|
|
|
|
self.isProcessingRequest = false
|
|
|
|
|
}
|
|
|
|
|
do {
|
|
|
|
|
let fetchedCollections = try result.get()
|
|
|
|
|
for fetchedCollection in fetchedCollections {
|
|
|
|
|
DispatchQueue.main.async {
|
2021-10-08 21:15:38 +00:00
|
|
|
|
let localCollection = WFACollection(context: LocalStorageManager.standard.container.viewContext)
|
2021-02-01 18:46:25 +00:00
|
|
|
|
localCollection.alias = fetchedCollection.alias
|
|
|
|
|
localCollection.blogDescription = fetchedCollection.description
|
|
|
|
|
localCollection.email = fetchedCollection.email
|
|
|
|
|
localCollection.isPublic = fetchedCollection.isPublic ?? false
|
|
|
|
|
localCollection.styleSheet = fetchedCollection.styleSheet
|
|
|
|
|
localCollection.title = fetchedCollection.title
|
|
|
|
|
localCollection.url = fetchedCollection.url
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
DispatchQueue.main.async {
|
2021-10-08 21:07:06 +00:00
|
|
|
|
LocalStorageManager.standard.saveContext()
|
2021-02-01 18:46:25 +00:00
|
|
|
|
}
|
|
|
|
|
} catch WFError.unauthorized {
|
2022-07-27 13:56:32 +00:00
|
|
|
|
self.currentError = AccountError.genericAuthError
|
2021-02-01 18:46:25 +00:00
|
|
|
|
self.logout()
|
|
|
|
|
} catch {
|
2022-07-27 13:56:32 +00:00
|
|
|
|
self.currentError = AppError.genericError(error.localizedDescription)
|
2021-02-01 18:46:25 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-10 11:33:44 +00:00
|
|
|
|
// swiftlint:disable function_body_length
|
2021-02-01 18:46:25 +00:00
|
|
|
|
func fetchUserPostsHandler(result: Result<[WFPost], Error>) {
|
|
|
|
|
// We're done with the network request.
|
|
|
|
|
DispatchQueue.main.async {
|
|
|
|
|
self.isProcessingRequest = false
|
|
|
|
|
}
|
|
|
|
|
let request = WFAPost.createFetchRequest()
|
|
|
|
|
do {
|
2021-10-08 21:15:38 +00:00
|
|
|
|
let locallyCachedPosts = try LocalStorageManager.standard.container.viewContext.fetch(request)
|
2021-02-01 18:46:25 +00:00
|
|
|
|
do {
|
|
|
|
|
var postsToDelete = locallyCachedPosts.filter { $0.status != PostStatus.local.rawValue }
|
|
|
|
|
let fetchedPosts = try result.get()
|
|
|
|
|
for fetchedPost in fetchedPosts {
|
|
|
|
|
if let managedPost = locallyCachedPosts.first(where: { $0.postId == fetchedPost.postId }) {
|
|
|
|
|
DispatchQueue.main.async {
|
|
|
|
|
managedPost.wasDeletedFromServer = false
|
|
|
|
|
if let fetchedPostUpdatedDate = fetchedPost.updatedDate,
|
|
|
|
|
let localPostUpdatedDate = managedPost.updatedDate {
|
|
|
|
|
managedPost.hasNewerRemoteCopy = fetchedPostUpdatedDate > localPostUpdatedDate
|
2022-07-27 13:56:32 +00:00
|
|
|
|
} else {
|
|
|
|
|
self.currentError = AppError.genericError(
|
|
|
|
|
"Error updating post: could not determine which copy of post is newer."
|
|
|
|
|
)
|
|
|
|
|
}
|
2023-06-10 11:33:44 +00:00
|
|
|
|
if managedPost.collectionAlias != fetchedPost.collectionAlias {
|
|
|
|
|
// The post has been moved so we update the managed post's collectionAlias property.
|
|
|
|
|
DispatchQueue.main.async {
|
|
|
|
|
if self.selectedPost == managedPost {
|
|
|
|
|
self.selectedPost = nil
|
|
|
|
|
}
|
|
|
|
|
managedPost.collectionAlias = fetchedPost.collectionAlias
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-02-01 18:46:25 +00:00
|
|
|
|
postsToDelete.removeAll(where: { $0.postId == fetchedPost.postId })
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
DispatchQueue.main.async {
|
2021-10-08 21:15:38 +00:00
|
|
|
|
let managedPost = WFAPost(context: LocalStorageManager.standard.container.viewContext)
|
2022-04-02 12:04:50 +00:00
|
|
|
|
self.importData(from: fetchedPost, into: managedPost)
|
2021-02-01 18:46:25 +00:00
|
|
|
|
managedPost.collectionAlias = fetchedPost.collectionAlias
|
|
|
|
|
managedPost.wasDeletedFromServer = false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
DispatchQueue.main.async {
|
|
|
|
|
for post in postsToDelete { post.wasDeletedFromServer = true }
|
2021-10-08 21:07:06 +00:00
|
|
|
|
LocalStorageManager.standard.saveContext()
|
2021-02-01 18:46:25 +00:00
|
|
|
|
}
|
|
|
|
|
} catch {
|
2022-07-27 13:56:32 +00:00
|
|
|
|
self.currentError = AppError.genericError(error.localizedDescription)
|
2021-02-01 18:46:25 +00:00
|
|
|
|
}
|
|
|
|
|
} catch WFError.unauthorized {
|
2022-07-27 13:56:32 +00:00
|
|
|
|
self.currentError = AccountError.genericAuthError
|
2021-02-01 18:46:25 +00:00
|
|
|
|
self.logout()
|
|
|
|
|
} catch {
|
2022-07-27 13:56:32 +00:00
|
|
|
|
self.currentError = LocalStoreError.couldNotFetchPosts("cached")
|
2021-02-01 18:46:25 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2023-06-10 11:33:44 +00:00
|
|
|
|
// swiftlint:enable function_body_length
|
2021-02-01 18:46:25 +00:00
|
|
|
|
|
|
|
|
|
func publishHandler(result: Result<WFPost, Error>) {
|
|
|
|
|
// We're done with the network request.
|
|
|
|
|
DispatchQueue.main.async {
|
|
|
|
|
self.isProcessingRequest = false
|
|
|
|
|
}
|
|
|
|
|
// ⚠️ NOTE:
|
|
|
|
|
// The API does not return a collection alias, so we take care not to overwrite the
|
|
|
|
|
// cached post's collection alias with the 'nil' value from the fetched post.
|
|
|
|
|
// See: https://github.com/writeas/writefreely-swift/issues/20
|
|
|
|
|
do {
|
|
|
|
|
let fetchedPost = try result.get()
|
|
|
|
|
// If this is an updated post, check it against postToUpdate.
|
|
|
|
|
if let updatingPost = self.postToUpdate {
|
2022-04-02 12:04:50 +00:00
|
|
|
|
importData(from: fetchedPost, into: updatingPost)
|
2021-02-01 18:46:25 +00:00
|
|
|
|
DispatchQueue.main.async {
|
2021-10-08 21:07:06 +00:00
|
|
|
|
LocalStorageManager.standard.saveContext()
|
2021-02-01 18:46:25 +00:00
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// Otherwise if it's a newly-published post, find it in the local store.
|
|
|
|
|
let request = WFAPost.createFetchRequest()
|
|
|
|
|
let matchBodyPredicate = NSPredicate(format: "body == %@", fetchedPost.body)
|
|
|
|
|
if let fetchedPostTitle = fetchedPost.title {
|
|
|
|
|
let matchTitlePredicate = NSPredicate(format: "title == %@", fetchedPostTitle)
|
|
|
|
|
request.predicate = NSCompoundPredicate(
|
|
|
|
|
andPredicateWithSubpredicates: [
|
|
|
|
|
matchTitlePredicate,
|
|
|
|
|
matchBodyPredicate
|
|
|
|
|
]
|
|
|
|
|
)
|
|
|
|
|
} else {
|
|
|
|
|
request.predicate = matchBodyPredicate
|
|
|
|
|
}
|
|
|
|
|
do {
|
2021-10-08 21:15:38 +00:00
|
|
|
|
let cachedPostsResults = try LocalStorageManager.standard.container.viewContext.fetch(request)
|
2021-02-01 18:46:25 +00:00
|
|
|
|
guard let cachedPost = cachedPostsResults.first else { return }
|
2022-04-02 12:04:50 +00:00
|
|
|
|
importData(from: fetchedPost, into: cachedPost)
|
2021-02-01 18:46:25 +00:00
|
|
|
|
DispatchQueue.main.async {
|
2021-10-08 21:07:06 +00:00
|
|
|
|
LocalStorageManager.standard.saveContext()
|
2021-02-01 18:46:25 +00:00
|
|
|
|
}
|
|
|
|
|
} catch {
|
2022-07-27 13:56:32 +00:00
|
|
|
|
self.currentError = LocalStoreError.couldNotFetchPosts("cached")
|
2021-02-01 18:46:25 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} catch {
|
2022-07-27 13:56:32 +00:00
|
|
|
|
self.currentError = AppError.genericError(error.localizedDescription)
|
2021-02-01 18:46:25 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func updateFromServerHandler(result: Result<WFPost, Error>) {
|
|
|
|
|
// We're done with the network request.
|
|
|
|
|
DispatchQueue.main.async {
|
|
|
|
|
self.isProcessingRequest = false
|
|
|
|
|
}
|
|
|
|
|
// ⚠️ NOTE:
|
|
|
|
|
// The API does not return a collection alias, so we take care not to overwrite the
|
|
|
|
|
// cached post's collection alias with the 'nil' value from the fetched post.
|
|
|
|
|
// See: https://github.com/writeas/writefreely-swift/issues/20
|
|
|
|
|
do {
|
|
|
|
|
let fetchedPost = try result.get()
|
2023-04-16 10:08:32 +00:00
|
|
|
|
#if os(iOS)
|
2021-02-01 18:46:25 +00:00
|
|
|
|
guard let cachedPost = self.selectedPost else { return }
|
2023-04-16 10:08:32 +00:00
|
|
|
|
#else
|
|
|
|
|
guard let cachedPost = self.editor.postToUpdate else { return }
|
|
|
|
|
#endif
|
2022-04-02 12:04:50 +00:00
|
|
|
|
importData(from: fetchedPost, into: cachedPost)
|
2021-02-01 18:46:25 +00:00
|
|
|
|
cachedPost.hasNewerRemoteCopy = false
|
|
|
|
|
DispatchQueue.main.async {
|
2021-10-08 21:07:06 +00:00
|
|
|
|
LocalStorageManager.standard.saveContext()
|
2023-04-16 10:08:32 +00:00
|
|
|
|
#if os(macOS)
|
|
|
|
|
self.selectedPost = cachedPost
|
|
|
|
|
#endif
|
2023-07-23 11:19:52 +00:00
|
|
|
|
cachedPost.status = PostStatus.published.rawValue
|
2021-02-01 18:46:25 +00:00
|
|
|
|
}
|
|
|
|
|
} catch {
|
2022-07-27 13:56:32 +00:00
|
|
|
|
self.currentError = AppError.genericError(error.localizedDescription)
|
2021-02-01 18:46:25 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func movePostHandler(result: Result<Bool, Error>) {
|
|
|
|
|
// We're done with the network request.
|
|
|
|
|
DispatchQueue.main.async {
|
|
|
|
|
self.isProcessingRequest = false
|
|
|
|
|
}
|
|
|
|
|
do {
|
|
|
|
|
let succeeded = try result.get()
|
|
|
|
|
if succeeded {
|
|
|
|
|
if let post = selectedPost {
|
|
|
|
|
updateFromServer(post: post)
|
|
|
|
|
} else {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} catch {
|
|
|
|
|
DispatchQueue.main.async {
|
2021-10-08 21:15:38 +00:00
|
|
|
|
LocalStorageManager.standard.container.viewContext.rollback()
|
2021-02-01 18:46:25 +00:00
|
|
|
|
}
|
2022-07-27 13:56:32 +00:00
|
|
|
|
self.currentError = AppError.genericError(error.localizedDescription)
|
2021-02-01 18:46:25 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2022-04-02 12:04:50 +00:00
|
|
|
|
|
|
|
|
|
private func importData(from fetchedPost: WFPost, into cachedPost: WFAPost) {
|
|
|
|
|
cachedPost.appearance = fetchedPost.appearance
|
|
|
|
|
cachedPost.body = fetchedPost.body
|
|
|
|
|
cachedPost.createdDate = fetchedPost.createdDate
|
|
|
|
|
cachedPost.language = fetchedPost.language
|
|
|
|
|
cachedPost.postId = fetchedPost.postId
|
|
|
|
|
cachedPost.rtl = fetchedPost.rtl ?? false
|
|
|
|
|
cachedPost.slug = fetchedPost.slug
|
2023-07-23 14:22:35 +00:00
|
|
|
|
cachedPost.status = PostStatus.published.rawValue
|
2022-04-02 12:04:50 +00:00
|
|
|
|
cachedPost.title = fetchedPost.title ?? ""
|
|
|
|
|
cachedPost.updatedDate = fetchedPost.updatedDate
|
|
|
|
|
}
|
2021-02-01 18:46:25 +00:00
|
|
|
|
}
|