Source code for the WriteFreely SwiftUI app for iOS, iPadOS, and macOS
Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.
 
 
 

302 linhas
14 KiB

  1. import Foundation
  2. import WriteFreely
  3. extension WriteFreelyModel {
  4. func loginHandler(result: Result<WFUser, Error>) {
  5. DispatchQueue.main.async {
  6. self.isLoggingIn = false
  7. }
  8. do {
  9. let user = try result.get()
  10. fetchUserCollections()
  11. fetchUserPosts()
  12. do {
  13. try saveTokenToKeychain(user.token, username: user.username, server: account.server)
  14. DispatchQueue.main.async {
  15. self.account.login(user)
  16. }
  17. } catch {
  18. DispatchQueue.main.async {
  19. self.loginErrorMessage = "There was a problem storing your access token to the Keychain."
  20. self.isPresentingLoginErrorAlert = true
  21. }
  22. }
  23. } catch WFError.notFound {
  24. DispatchQueue.main.async {
  25. self.loginErrorMessage = AccountError.usernameNotFound.localizedDescription
  26. self.isPresentingLoginErrorAlert = true
  27. }
  28. } catch WFError.unauthorized {
  29. DispatchQueue.main.async {
  30. self.loginErrorMessage = AccountError.invalidPassword.localizedDescription
  31. self.isPresentingLoginErrorAlert = true
  32. }
  33. } catch {
  34. if (error as NSError).domain == NSURLErrorDomain,
  35. (error as NSError).code == -1003 {
  36. DispatchQueue.main.async {
  37. self.loginErrorMessage = AccountError.serverNotFound.localizedDescription
  38. self.isPresentingLoginErrorAlert = true
  39. }
  40. } else {
  41. DispatchQueue.main.async {
  42. self.loginErrorMessage = error.localizedDescription
  43. self.isPresentingLoginErrorAlert = true
  44. }
  45. }
  46. }
  47. }
  48. func logoutHandler(result: Result<Bool, Error>) {
  49. do {
  50. _ = try result.get()
  51. do {
  52. try purgeTokenFromKeychain(username: account.user?.username, server: account.server)
  53. client = nil
  54. DispatchQueue.main.async {
  55. self.account.logout()
  56. LocalStorageManager.standard.purgeUserCollections()
  57. self.posts.purgePublishedPosts()
  58. }
  59. } catch {
  60. print("Something went wrong purging the token from the Keychain.")
  61. }
  62. } catch WFError.notFound {
  63. // The user token is invalid or doesn't exist, so it's been invalidated by the server. Proceed with
  64. // purging the token from the Keychain, destroying the client object, and setting the AccountModel to its
  65. // logged-out state.
  66. do {
  67. try purgeTokenFromKeychain(username: account.user?.username, server: account.server)
  68. client = nil
  69. DispatchQueue.main.async {
  70. self.account.logout()
  71. LocalStorageManager.standard.purgeUserCollections()
  72. self.posts.purgePublishedPosts()
  73. }
  74. } catch {
  75. print("Something went wrong purging the token from the Keychain.")
  76. }
  77. } catch {
  78. // We get a 'cannot parse response' (similar to what we were seeing in the Swift package) NSURLError here,
  79. // so we're using a hacky workaround — if we get the NSURLError, but the AccountModel still thinks we're
  80. // logged in, try calling the logout function again and see what we get.
  81. // Conditional cast from 'Error' to 'NSError' always succeeds but is the only way to check error properties.
  82. if (error as NSError).domain == NSURLErrorDomain,
  83. (error as NSError).code == NSURLErrorCannotParseResponse {
  84. if account.isLoggedIn {
  85. self.logout()
  86. }
  87. }
  88. }
  89. }
  90. func fetchUserCollectionsHandler(result: Result<[WFCollection], Error>) {
  91. // We're done with the network request.
  92. DispatchQueue.main.async {
  93. self.isProcessingRequest = false
  94. }
  95. do {
  96. let fetchedCollections = try result.get()
  97. for fetchedCollection in fetchedCollections {
  98. DispatchQueue.main.async {
  99. let localCollection = WFACollection(context: LocalStorageManager.standard.container.viewContext)
  100. localCollection.alias = fetchedCollection.alias
  101. localCollection.blogDescription = fetchedCollection.description
  102. localCollection.email = fetchedCollection.email
  103. localCollection.isPublic = fetchedCollection.isPublic ?? false
  104. localCollection.styleSheet = fetchedCollection.styleSheet
  105. localCollection.title = fetchedCollection.title
  106. localCollection.url = fetchedCollection.url
  107. }
  108. }
  109. DispatchQueue.main.async {
  110. LocalStorageManager.standard.saveContext()
  111. }
  112. } catch WFError.unauthorized {
  113. DispatchQueue.main.async {
  114. self.loginErrorMessage = "Something went wrong, please try logging in again."
  115. self.isPresentingLoginErrorAlert = true
  116. }
  117. self.logout()
  118. } catch {
  119. print(error)
  120. }
  121. }
  122. func fetchUserPostsHandler(result: Result<[WFPost], Error>) {
  123. // We're done with the network request.
  124. DispatchQueue.main.async {
  125. self.isProcessingRequest = false
  126. }
  127. let request = WFAPost.createFetchRequest()
  128. do {
  129. let locallyCachedPosts = try LocalStorageManager.standard.container.viewContext.fetch(request)
  130. do {
  131. var postsToDelete = locallyCachedPosts.filter { $0.status != PostStatus.local.rawValue }
  132. let fetchedPosts = try result.get()
  133. for fetchedPost in fetchedPosts {
  134. if let managedPost = locallyCachedPosts.first(where: { $0.postId == fetchedPost.postId }) {
  135. DispatchQueue.main.async {
  136. managedPost.wasDeletedFromServer = false
  137. if let fetchedPostUpdatedDate = fetchedPost.updatedDate,
  138. let localPostUpdatedDate = managedPost.updatedDate {
  139. managedPost.hasNewerRemoteCopy = fetchedPostUpdatedDate > localPostUpdatedDate
  140. } else { print("Error: could not determine which copy of post is newer") }
  141. postsToDelete.removeAll(where: { $0.postId == fetchedPost.postId })
  142. }
  143. } else {
  144. DispatchQueue.main.async {
  145. let managedPost = WFAPost(context: LocalStorageManager.standard.container.viewContext)
  146. managedPost.postId = fetchedPost.postId
  147. managedPost.slug = fetchedPost.slug
  148. managedPost.appearance = fetchedPost.appearance
  149. managedPost.language = fetchedPost.language
  150. managedPost.rtl = fetchedPost.rtl ?? false
  151. managedPost.createdDate = fetchedPost.createdDate
  152. managedPost.updatedDate = fetchedPost.updatedDate
  153. managedPost.title = fetchedPost.title ?? ""
  154. managedPost.body = fetchedPost.body
  155. managedPost.collectionAlias = fetchedPost.collectionAlias
  156. managedPost.status = PostStatus.published.rawValue
  157. managedPost.wasDeletedFromServer = false
  158. }
  159. }
  160. }
  161. DispatchQueue.main.async {
  162. for post in postsToDelete { post.wasDeletedFromServer = true }
  163. LocalStorageManager.standard.saveContext()
  164. }
  165. } catch {
  166. print(error)
  167. }
  168. } catch WFError.unauthorized {
  169. DispatchQueue.main.async {
  170. self.loginErrorMessage = "Something went wrong, please try logging in again."
  171. self.isPresentingLoginErrorAlert = true
  172. }
  173. self.logout()
  174. } catch {
  175. print("Error: Failed to fetch cached posts")
  176. }
  177. }
  178. func publishHandler(result: Result<WFPost, Error>) {
  179. // We're done with the network request.
  180. DispatchQueue.main.async {
  181. self.isProcessingRequest = false
  182. }
  183. // ⚠️ NOTE:
  184. // The API does not return a collection alias, so we take care not to overwrite the
  185. // cached post's collection alias with the 'nil' value from the fetched post.
  186. // See: https://github.com/writeas/writefreely-swift/issues/20
  187. do {
  188. let fetchedPost = try result.get()
  189. // If this is an updated post, check it against postToUpdate.
  190. if let updatingPost = self.postToUpdate {
  191. updatingPost.appearance = fetchedPost.appearance
  192. updatingPost.body = fetchedPost.body
  193. updatingPost.createdDate = fetchedPost.createdDate
  194. updatingPost.language = fetchedPost.language
  195. updatingPost.postId = fetchedPost.postId
  196. updatingPost.rtl = fetchedPost.rtl ?? false
  197. updatingPost.slug = fetchedPost.slug
  198. updatingPost.status = PostStatus.published.rawValue
  199. updatingPost.title = fetchedPost.title ?? ""
  200. updatingPost.updatedDate = fetchedPost.updatedDate
  201. DispatchQueue.main.async {
  202. LocalStorageManager.standard.saveContext()
  203. }
  204. } else {
  205. // Otherwise if it's a newly-published post, find it in the local store.
  206. let request = WFAPost.createFetchRequest()
  207. let matchBodyPredicate = NSPredicate(format: "body == %@", fetchedPost.body)
  208. if let fetchedPostTitle = fetchedPost.title {
  209. let matchTitlePredicate = NSPredicate(format: "title == %@", fetchedPostTitle)
  210. request.predicate = NSCompoundPredicate(
  211. andPredicateWithSubpredicates: [
  212. matchTitlePredicate,
  213. matchBodyPredicate
  214. ]
  215. )
  216. } else {
  217. request.predicate = matchBodyPredicate
  218. }
  219. do {
  220. let cachedPostsResults = try LocalStorageManager.standard.container.viewContext.fetch(request)
  221. guard let cachedPost = cachedPostsResults.first else { return }
  222. cachedPost.appearance = fetchedPost.appearance
  223. cachedPost.body = fetchedPost.body
  224. cachedPost.createdDate = fetchedPost.createdDate
  225. cachedPost.language = fetchedPost.language
  226. cachedPost.postId = fetchedPost.postId
  227. cachedPost.rtl = fetchedPost.rtl ?? false
  228. cachedPost.slug = fetchedPost.slug
  229. cachedPost.status = PostStatus.published.rawValue
  230. cachedPost.title = fetchedPost.title ?? ""
  231. cachedPost.updatedDate = fetchedPost.updatedDate
  232. DispatchQueue.main.async {
  233. LocalStorageManager.standard.saveContext()
  234. }
  235. } catch {
  236. print("Error: Failed to fetch cached posts")
  237. }
  238. }
  239. } catch {
  240. print(error)
  241. }
  242. }
  243. func updateFromServerHandler(result: Result<WFPost, Error>) {
  244. // We're done with the network request.
  245. DispatchQueue.main.async {
  246. self.isProcessingRequest = false
  247. }
  248. // ⚠️ NOTE:
  249. // The API does not return a collection alias, so we take care not to overwrite the
  250. // cached post's collection alias with the 'nil' value from the fetched post.
  251. // See: https://github.com/writeas/writefreely-swift/issues/20
  252. do {
  253. let fetchedPost = try result.get()
  254. guard let cachedPost = self.selectedPost else { return }
  255. cachedPost.appearance = fetchedPost.appearance
  256. cachedPost.body = fetchedPost.body
  257. cachedPost.createdDate = fetchedPost.createdDate
  258. cachedPost.language = fetchedPost.language
  259. cachedPost.postId = fetchedPost.postId
  260. cachedPost.rtl = fetchedPost.rtl ?? false
  261. cachedPost.slug = fetchedPost.slug
  262. cachedPost.status = PostStatus.published.rawValue
  263. cachedPost.title = fetchedPost.title ?? ""
  264. cachedPost.updatedDate = fetchedPost.updatedDate
  265. cachedPost.hasNewerRemoteCopy = false
  266. DispatchQueue.main.async {
  267. LocalStorageManager.standard.saveContext()
  268. }
  269. } catch {
  270. print(error)
  271. }
  272. }
  273. func movePostHandler(result: Result<Bool, Error>) {
  274. // We're done with the network request.
  275. DispatchQueue.main.async {
  276. self.isProcessingRequest = false
  277. }
  278. do {
  279. let succeeded = try result.get()
  280. if succeeded {
  281. if let post = selectedPost {
  282. updateFromServer(post: post)
  283. } else {
  284. return
  285. }
  286. }
  287. } catch {
  288. DispatchQueue.main.async {
  289. LocalStorageManager.standard.container.viewContext.rollback()
  290. }
  291. print(error)
  292. }
  293. }
  294. }