PendingMessagesManager.swift 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284
  1. // _____ _
  2. // |_ _| |_ _ _ ___ ___ _ __ __ _
  3. // | | | ' \| '_/ -_) -_) ' \/ _` |_
  4. // |_| |_||_|_| \___\___|_|_|_\__,_(_)
  5. //
  6. // Threema iOS Client
  7. // Copyright (c) 2018-2020 Threema GmbH
  8. //
  9. // This program is free software: you can redistribute it and/or modify
  10. // it under the terms of the GNU Affero General Public License, version 3,
  11. // as published by the Free Software Foundation.
  12. //
  13. // This program is distributed in the hope that it will be useful,
  14. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. // GNU Affero General Public License for more details.
  17. //
  18. // You should have received a copy of the GNU Affero General Public License
  19. // along with this program. If not, see <https://www.gnu.org/licenses/>.
  20. import Foundation
  21. import CocoaLumberjackSwift
  22. @objc class PendingMessagesManager: NSObject {
  23. typealias TimeOfDay = (hour: Int, minute: Int)
  24. @objc static let shared = PendingMessagesManager()
  25. private var pendingMessages: [String: PendingMessage]
  26. private var doneMessages: NSMutableOrderedSet
  27. @objc private override init() {
  28. pendingMessages = [String: PendingMessage]()
  29. doneMessages = NSMutableOrderedSet()
  30. }
  31. // MARK: Public functions
  32. @objc func setup() {
  33. loadPendingMessages()
  34. loadDoneMessages()
  35. }
  36. @objc func pendingMessage(senderId: String?, messageId: String?, abstractMessage: AbstractMessage?, threemaDict: [String: Any]?, completion: @escaping (_ pendingMessage: PendingMessage?) -> Void) {
  37. var evalKey: String?
  38. if let abstractMessage = abstractMessage,
  39. let fromIdentity = abstractMessage.fromIdentity,
  40. let messageId = abstractMessage.messageId {
  41. evalKey = fromIdentity + messageId.hexEncodedString()
  42. }
  43. else if let senderId = senderId,
  44. let messageId = messageId {
  45. evalKey = senderId + messageId
  46. }
  47. guard let key = evalKey, let senderIdentity = senderId ?? abstractMessage?.fromIdentity else {
  48. DDLogWarn("PendingMessagesManager.pendingMessage: Some arguments invalid, no key or sender identity could be evaluated")
  49. completion(nil)
  50. return
  51. }
  52. self.checkPendingMessages()
  53. var pendingMessage = pendingMessages[key]
  54. if let pendingMessage = pendingMessage {
  55. // check is notification is fired
  56. pendingMessage.isPendingNotification { pending in
  57. if pending == false {
  58. // not in notificationcenter or triggered
  59. if pendingMessage.isMessageAlreadyPushed() {
  60. // move message to done message
  61. self.pendingMessageIsDone(pendingMessage: pendingMessage, cancelTask: true)
  62. completion(nil)
  63. return
  64. } else {
  65. // check is fire date over
  66. var alreadyFired = false
  67. if let fireDate = pendingMessage.fireDate, fireDate < Date() {
  68. // push was already fired
  69. alreadyFired = true
  70. self.pendingMessageIsDone(pendingMessage: pendingMessage, cancelTask: true)
  71. completion(nil)
  72. return
  73. }
  74. if alreadyFired == false {
  75. if let abstractMessage = abstractMessage {
  76. pendingMessage.addAbstractMessage(message: abstractMessage)
  77. }
  78. }
  79. completion(pendingMessage)
  80. return
  81. }
  82. } else {
  83. if let abstractMessage = abstractMessage {
  84. pendingMessage.addAbstractMessage(message: abstractMessage)
  85. }
  86. completion(pendingMessage)
  87. return
  88. }
  89. }
  90. } else {
  91. if doneMessages.contains(key) {
  92. // message is already done
  93. completion(nil)
  94. return
  95. } else {
  96. // create one
  97. if let abstractMessage = abstractMessage {
  98. pendingMessage = PendingMessage(receivedAbstractMessage: abstractMessage)
  99. } else {
  100. if let threemaDict = threemaDict {
  101. pendingMessage = PendingMessage(senderIdentity: senderIdentity, messageIdentity: messageId!, pushPayload: threemaDict)
  102. } else {
  103. pendingMessage = PendingMessage(senderIdentity: senderIdentity, messageIdentity: messageId!)
  104. }
  105. }
  106. pendingMessages.updateValue(pendingMessage!, forKey: pendingMessage!.key)
  107. pendingMessage?.startInitialTimedNotification()
  108. completion(pendingMessages[key])
  109. return
  110. }
  111. }
  112. }
  113. func pendingMessageIsDone(pendingMessage: PendingMessage, cancelTask: Bool) {
  114. if doneMessages.count >= 300 {
  115. let range: NSRange = NSRange(location: 0, length: doneMessages.count - 299)
  116. doneMessages.removeObjects(in: range)
  117. }
  118. doneMessages.add(pendingMessage.key)
  119. pendingMessages.removeValue(forKey: pendingMessage.key)
  120. if pendingMessages.count == 0 {
  121. BackgroundTaskManager.shared.cancelBackgroundTask(key: kAppPushBackgroundTask)
  122. }
  123. }
  124. @objc func save() {
  125. if Thread.isMainThread {
  126. saveAll()
  127. } else {
  128. DispatchQueue.main.sync {
  129. saveAll()
  130. }
  131. }
  132. }
  133. @objc class func canMasterDndSendPush() -> Bool {
  134. if LicenseStore.requiresLicenseKey() {
  135. if UserSettings.shared().enableMasterDnd {
  136. let calendar = Calendar.current
  137. let currentDate = Date()
  138. let currentWeekDay = calendar.component(.weekday, from: currentDate)
  139. let selectedWorkingDays = UserSettings.shared().masterDndWorkingDays
  140. if selectedWorkingDays!.contains(currentWeekDay) {
  141. let currentTime = TimeOfDay(hour: calendar.component(.hour, from: currentDate), minute:calendar.component(.minute, from: currentDate))
  142. let startTime = timeOfDayFromTimeString(timeString: UserSettings.shared().masterDndStartTime)
  143. let endTime = timeOfDayFromTimeString(timeString: UserSettings.shared().masterDndEndTime)
  144. if currentTime >= startTime && currentTime <= endTime {
  145. return true
  146. }
  147. }
  148. return false
  149. }
  150. }
  151. return true
  152. }
  153. // MARK: Private functions
  154. /// Mark all handled pending messages as done
  155. private func checkPendingMessages() {
  156. for (_, pendingMessage) in pendingMessages {
  157. if pendingMessage.isMessageAlreadyPushed() == true {
  158. pendingMessageIsDone(pendingMessage: pendingMessage, cancelTask: false)
  159. } else {
  160. if let fireDate = pendingMessage.fireDate {
  161. if fireDate < Date() {
  162. pendingMessageIsDone(pendingMessage: pendingMessage, cancelTask: false)
  163. }
  164. }
  165. }
  166. }
  167. }
  168. private func saveAll() {
  169. let savePathPendingMessages = self.savePathPendingMessages()
  170. do {
  171. if FileManager.default.fileExists(atPath: savePathPendingMessages) {
  172. try FileManager.default.removeItem(atPath: savePathPendingMessages)
  173. }
  174. } catch {
  175. DDLogError("Unable to delete pending messages file: \(error.localizedDescription)")
  176. }
  177. if self.pendingMessages.count > 0 {
  178. NSKeyedArchiver.archiveRootObject(self.pendingMessages, toFile: savePathPendingMessages)
  179. }
  180. let savePathDoneMessages = self.savePathDoneMessages()
  181. do {
  182. if FileManager.default.fileExists(atPath: savePathDoneMessages) {
  183. try FileManager.default.removeItem(atPath: savePathDoneMessages)
  184. }
  185. } catch {
  186. DDLogError("Unable to delete done messages file: \(error.localizedDescription)")
  187. }
  188. if self.doneMessages.count > 0 {
  189. NSKeyedArchiver.archiveRootObject(self.doneMessages, toFile: savePathDoneMessages)
  190. }
  191. }
  192. private func loadPendingMessages() {
  193. DispatchQueue.main.async {
  194. let savePath = self.savePathPendingMessages()
  195. if FileManager.default.fileExists(atPath: savePath) {
  196. if let savedPendingMessages = NSKeyedUnarchiver.unarchiveObject(withFile: savePath) as? [String: PendingMessage] {
  197. for pendingMessage in savedPendingMessages {
  198. if (pendingMessage.value.fireDate == nil) {
  199. continue
  200. }
  201. self.pendingMessages[pendingMessage.key] = pendingMessage.value
  202. }
  203. }
  204. do {
  205. try FileManager.default.removeItem(atPath: savePath)
  206. } catch {
  207. DDLogError("Unable to delete pending messages file: \(error.localizedDescription)")
  208. }
  209. }
  210. }
  211. }
  212. private func loadDoneMessages() {
  213. DispatchQueue.main.async {
  214. let savePath = self.savePathDoneMessages()
  215. if FileManager.default.fileExists(atPath: savePath) {
  216. let savedDoneMessages = NSKeyedUnarchiver.unarchiveObject(withFile: savePath)
  217. if let savedDoneMessagesDict = savedDoneMessages as? [String: PendingMessage] {
  218. for doneMessage in savedDoneMessagesDict {
  219. self.doneMessages.add(doneMessage.key)
  220. }
  221. }
  222. else if let savedDoneMessagesSet = savedDoneMessages as? NSMutableOrderedSet {
  223. self.doneMessages.addObjects(from: savedDoneMessagesSet.array)
  224. }
  225. do {
  226. try FileManager.default.removeItem(atPath: savePath)
  227. } catch {
  228. DDLogError("Unable to delete done messages file: \(error.localizedDescription)")
  229. }
  230. }
  231. }
  232. }
  233. private func savePathPendingMessages() -> String {
  234. let documentDir = DocumentManager.documentsDirectory()
  235. return documentDir!.appendingPathComponent("PendingMessages").path
  236. }
  237. private func savePathDoneMessages() -> String {
  238. let documentDir = DocumentManager.documentsDirectory()
  239. return documentDir!.appendingPathComponent("DoneMessages").path
  240. }
  241. private class func timeOfDayFromTimeString(timeString: String) -> TimeOfDay {
  242. let components: [String] = timeString.components(separatedBy: ":")
  243. return TimeOfDay(hour: Int(components[0])!, minute: Int(components[1])!)
  244. }
  245. }