123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284 |
- // _____ _
- // |_ _| |_ _ _ ___ ___ _ __ __ _
- // | | | ' \| '_/ -_) -_) ' \/ _` |_
- // |_| |_||_|_| \___\___|_|_|_\__,_(_)
- //
- // Threema iOS Client
- // Copyright (c) 2018-2020 Threema GmbH
- //
- // This program is free software: you can redistribute it and/or modify
- // it under the terms of the GNU Affero General Public License, version 3,
- // as published by the Free Software Foundation.
- //
- // This program is distributed in the hope that it will be useful,
- // but WITHOUT ANY WARRANTY; without even the implied warranty of
- // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- // GNU Affero General Public License for more details.
- //
- // You should have received a copy of the GNU Affero General Public License
- // along with this program. If not, see <https://www.gnu.org/licenses/>.
- import Foundation
- import CocoaLumberjackSwift
- @objc class PendingMessagesManager: NSObject {
-
- typealias TimeOfDay = (hour: Int, minute: Int)
-
- @objc static let shared = PendingMessagesManager()
-
- private var pendingMessages: [String: PendingMessage]
- private var doneMessages: NSMutableOrderedSet
-
- @objc private override init() {
- pendingMessages = [String: PendingMessage]()
- doneMessages = NSMutableOrderedSet()
- }
-
-
- // MARK: Public functions
-
- @objc func setup() {
- loadPendingMessages()
- loadDoneMessages()
- }
-
- @objc func pendingMessage(senderId: String?, messageId: String?, abstractMessage: AbstractMessage?, threemaDict: [String: Any]?, completion: @escaping (_ pendingMessage: PendingMessage?) -> Void) {
-
- var evalKey: String?
- if let abstractMessage = abstractMessage,
- let fromIdentity = abstractMessage.fromIdentity,
- let messageId = abstractMessage.messageId {
-
- evalKey = fromIdentity + messageId.hexEncodedString()
- }
- else if let senderId = senderId,
- let messageId = messageId {
-
- evalKey = senderId + messageId
- }
-
- guard let key = evalKey, let senderIdentity = senderId ?? abstractMessage?.fromIdentity else {
- DDLogWarn("PendingMessagesManager.pendingMessage: Some arguments invalid, no key or sender identity could be evaluated")
- completion(nil)
- return
- }
-
- self.checkPendingMessages()
-
- var pendingMessage = pendingMessages[key]
- if let pendingMessage = pendingMessage {
- // check is notification is fired
- pendingMessage.isPendingNotification { pending in
- if pending == false {
- // not in notificationcenter or triggered
- if pendingMessage.isMessageAlreadyPushed() {
- // move message to done message
- self.pendingMessageIsDone(pendingMessage: pendingMessage, cancelTask: true)
- completion(nil)
- return
- } else {
- // check is fire date over
- var alreadyFired = false
- if let fireDate = pendingMessage.fireDate, fireDate < Date() {
- // push was already fired
- alreadyFired = true
- self.pendingMessageIsDone(pendingMessage: pendingMessage, cancelTask: true)
- completion(nil)
- return
- }
- if alreadyFired == false {
- if let abstractMessage = abstractMessage {
- pendingMessage.addAbstractMessage(message: abstractMessage)
- }
- }
-
- completion(pendingMessage)
- return
- }
- } else {
- if let abstractMessage = abstractMessage {
- pendingMessage.addAbstractMessage(message: abstractMessage)
- }
- completion(pendingMessage)
- return
- }
- }
- } else {
- if doneMessages.contains(key) {
- // message is already done
- completion(nil)
- return
- } else {
- // create one
- if let abstractMessage = abstractMessage {
- pendingMessage = PendingMessage(receivedAbstractMessage: abstractMessage)
- } else {
- if let threemaDict = threemaDict {
- pendingMessage = PendingMessage(senderIdentity: senderIdentity, messageIdentity: messageId!, pushPayload: threemaDict)
- } else {
- pendingMessage = PendingMessage(senderIdentity: senderIdentity, messageIdentity: messageId!)
- }
- }
-
- pendingMessages.updateValue(pendingMessage!, forKey: pendingMessage!.key)
- pendingMessage?.startInitialTimedNotification()
- completion(pendingMessages[key])
- return
- }
- }
- }
-
- func pendingMessageIsDone(pendingMessage: PendingMessage, cancelTask: Bool) {
- if doneMessages.count >= 300 {
- let range: NSRange = NSRange(location: 0, length: doneMessages.count - 299)
- doneMessages.removeObjects(in: range)
- }
- doneMessages.add(pendingMessage.key)
- pendingMessages.removeValue(forKey: pendingMessage.key)
-
- if pendingMessages.count == 0 {
- BackgroundTaskManager.shared.cancelBackgroundTask(key: kAppPushBackgroundTask)
- }
- }
-
- @objc func save() {
- if Thread.isMainThread {
- saveAll()
- } else {
- DispatchQueue.main.sync {
- saveAll()
- }
- }
- }
-
- @objc class func canMasterDndSendPush() -> Bool {
- if LicenseStore.requiresLicenseKey() {
- if UserSettings.shared().enableMasterDnd {
- let calendar = Calendar.current
- let currentDate = Date()
- let currentWeekDay = calendar.component(.weekday, from: currentDate)
- let selectedWorkingDays = UserSettings.shared().masterDndWorkingDays
- if selectedWorkingDays!.contains(currentWeekDay) {
- let currentTime = TimeOfDay(hour: calendar.component(.hour, from: currentDate), minute:calendar.component(.minute, from: currentDate))
- let startTime = timeOfDayFromTimeString(timeString: UserSettings.shared().masterDndStartTime)
- let endTime = timeOfDayFromTimeString(timeString: UserSettings.shared().masterDndEndTime)
-
- if currentTime >= startTime && currentTime <= endTime {
- return true
- }
-
- }
- return false
- }
- }
-
- return true
- }
-
-
- // MARK: Private functions
-
- /// Mark all handled pending messages as done
- private func checkPendingMessages() {
- for (_, pendingMessage) in pendingMessages {
- if pendingMessage.isMessageAlreadyPushed() == true {
- pendingMessageIsDone(pendingMessage: pendingMessage, cancelTask: false)
- } else {
- if let fireDate = pendingMessage.fireDate {
- if fireDate < Date() {
- pendingMessageIsDone(pendingMessage: pendingMessage, cancelTask: false)
- }
- }
- }
- }
- }
-
- private func saveAll() {
- let savePathPendingMessages = self.savePathPendingMessages()
- do {
- if FileManager.default.fileExists(atPath: savePathPendingMessages) {
- try FileManager.default.removeItem(atPath: savePathPendingMessages)
- }
- } catch {
- DDLogError("Unable to delete pending messages file: \(error.localizedDescription)")
- }
-
- if self.pendingMessages.count > 0 {
- NSKeyedArchiver.archiveRootObject(self.pendingMessages, toFile: savePathPendingMessages)
- }
-
- let savePathDoneMessages = self.savePathDoneMessages()
- do {
- if FileManager.default.fileExists(atPath: savePathDoneMessages) {
- try FileManager.default.removeItem(atPath: savePathDoneMessages)
- }
- } catch {
- DDLogError("Unable to delete done messages file: \(error.localizedDescription)")
- }
-
- if self.doneMessages.count > 0 {
- NSKeyedArchiver.archiveRootObject(self.doneMessages, toFile: savePathDoneMessages)
- }
- }
-
- private func loadPendingMessages() {
- DispatchQueue.main.async {
- let savePath = self.savePathPendingMessages()
- if FileManager.default.fileExists(atPath: savePath) {
- if let savedPendingMessages = NSKeyedUnarchiver.unarchiveObject(withFile: savePath) as? [String: PendingMessage] {
- for pendingMessage in savedPendingMessages {
- if (pendingMessage.value.fireDate == nil) {
- continue
- }
- self.pendingMessages[pendingMessage.key] = pendingMessage.value
- }
- }
- do {
- try FileManager.default.removeItem(atPath: savePath)
- } catch {
- DDLogError("Unable to delete pending messages file: \(error.localizedDescription)")
- }
- }
- }
- }
-
- private func loadDoneMessages() {
- DispatchQueue.main.async {
- let savePath = self.savePathDoneMessages()
- if FileManager.default.fileExists(atPath: savePath) {
- let savedDoneMessages = NSKeyedUnarchiver.unarchiveObject(withFile: savePath)
-
- if let savedDoneMessagesDict = savedDoneMessages as? [String: PendingMessage] {
- for doneMessage in savedDoneMessagesDict {
- self.doneMessages.add(doneMessage.key)
- }
- }
- else if let savedDoneMessagesSet = savedDoneMessages as? NSMutableOrderedSet {
- self.doneMessages.addObjects(from: savedDoneMessagesSet.array)
- }
- do {
- try FileManager.default.removeItem(atPath: savePath)
- } catch {
- DDLogError("Unable to delete done messages file: \(error.localizedDescription)")
- }
- }
- }
- }
-
- private func savePathPendingMessages() -> String {
- let documentDir = DocumentManager.documentsDirectory()
- return documentDir!.appendingPathComponent("PendingMessages").path
- }
-
- private func savePathDoneMessages() -> String {
- let documentDir = DocumentManager.documentsDirectory()
- return documentDir!.appendingPathComponent("DoneMessages").path
- }
-
- private class func timeOfDayFromTimeString(timeString: String) -> TimeOfDay {
- let components: [String] = timeString.components(separatedBy: ":")
- return TimeOfDay(hour: Int(components[0])!, minute: Int(components[1])!)
- }
- }
|