diff --git a/Riot.xcodeproj/project.pbxproj b/Riot.xcodeproj/project.pbxproj index d488d66c8..5d27863ad 100644 --- a/Riot.xcodeproj/project.pbxproj +++ b/Riot.xcodeproj/project.pbxproj @@ -787,6 +787,9 @@ EC3B066A24AC6ADE000DF9BF /* CrossSigningSetupBannerCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = EC3B066624AC6ADD000DF9BF /* CrossSigningSetupBannerCell.xib */; }; EC3B066B24AC6ADE000DF9BF /* CrossSigningBannerPreferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC3B066724AC6ADD000DF9BF /* CrossSigningBannerPreferences.swift */; }; EC3B066C24AC6ADE000DF9BF /* CrossSigningSetupBannerCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC3B066824AC6ADD000DF9BF /* CrossSigningSetupBannerCell.swift */; }; + EC45DB1725344B8A006911C1 /* MXSyncResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC45DB1525344B89006911C1 /* MXSyncResponse.swift */; }; + EC45DB1825344B8A006911C1 /* SyncResponseStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC45DB1625344B89006911C1 /* SyncResponseStore.swift */; }; + EC45DB1925344B8E006911C1 /* SyncResponseStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC45DB1625344B89006911C1 /* SyncResponseStore.swift */; }; EC49F5F92515016F003894A6 /* RoomInfoBasicViewData.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC49F5F82515016F003894A6 /* RoomInfoBasicViewData.swift */; }; EC51E78D250FC15000AAE7DB /* RoomCreationEventRowViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC51E78C250FC15000AAE7DB /* RoomCreationEventRowViewModel.swift */; }; EC51E7902510B7C700AAE7DB /* AutosizedTableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC51E78F2510B7C700AAE7DB /* AutosizedTableView.swift */; }; @@ -2011,6 +2014,8 @@ EC3B066624AC6ADD000DF9BF /* CrossSigningSetupBannerCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = CrossSigningSetupBannerCell.xib; sourceTree = ""; }; EC3B066724AC6ADD000DF9BF /* CrossSigningBannerPreferences.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CrossSigningBannerPreferences.swift; sourceTree = ""; }; EC3B066824AC6ADD000DF9BF /* CrossSigningSetupBannerCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CrossSigningSetupBannerCell.swift; sourceTree = ""; }; + EC45DB1525344B89006911C1 /* MXSyncResponse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MXSyncResponse.swift; sourceTree = ""; }; + EC45DB1625344B89006911C1 /* SyncResponseStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SyncResponseStore.swift; sourceTree = ""; }; EC49F5F82515016F003894A6 /* RoomInfoBasicViewData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomInfoBasicViewData.swift; sourceTree = ""; }; EC51E78C250FC15000AAE7DB /* RoomCreationEventRowViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomCreationEventRowViewModel.swift; sourceTree = ""; }; EC51E78F2510B7C700AAE7DB /* AutosizedTableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutosizedTableView.swift; sourceTree = ""; }; @@ -5139,6 +5144,8 @@ EC85D74E2477E614002C44C9 /* RiotNSE.entitlements */, EC85D7452477E5F7002C44C9 /* NotificationService.swift */, EC85D756247E700F002C44C9 /* NSEMemoryStore.swift */, + EC45DB1525344B89006911C1 /* MXSyncResponse.swift */, + EC45DB1625344B89006911C1 /* SyncResponseStore.swift */, EC85D7472477E5F7002C44C9 /* Info.plist */, EC1CA8D924D811B400DE9EBF /* NSE-Common.xcconfig */, EC1CA8BC24D1B4CF00DE9EBF /* NSE-Debug.xcconfig */, @@ -6163,11 +6170,13 @@ EC31F09C2524AE1400D407DA /* BiometricsAuthenticationPresenter.swift in Sources */, EC85D752247C0F52002C44C9 /* UNUserNotificationCenter.swift in Sources */, EC9A3EC724E1634100A8CFAE /* KeyValueStore.swift in Sources */, + EC45DB1725344B8A006911C1 /* MXSyncResponse.swift in Sources */, 32FD757A24D2C9BA00BA7B37 /* Bundle.swift in Sources */, EC31F0962521FC5300D407DA /* Strings.swift in Sources */, EC31F0972521FC6300D407DA /* Images.swift in Sources */, EC9A3EC824E1634800A8CFAE /* KeychainStore.swift in Sources */, EC31F0942521FC3700D407DA /* LocalAuthenticationService.swift in Sources */, + EC45DB1825344B8A006911C1 /* SyncResponseStore.swift in Sources */, 32FD755B24D15C7A00BA7B37 /* Configurable.swift in Sources */, EC85D755247C0F84002C44C9 /* Constants.swift in Sources */, ); @@ -6335,6 +6344,7 @@ 3291DC8D23E0BFF10009732F /* SecurityViewController.m in Sources */, B18DEDD4243377C10075FEF7 /* KeyVerificationSelfVerifyWaitViewModelType.swift in Sources */, B1C45A88232A8C2600165425 /* SettingsIdentityServerViewState.swift in Sources */, + EC45DB1925344B8E006911C1 /* SyncResponseStore.swift in Sources */, B1C782302500EAF500337EB9 /* TabBarCoordinatorType.swift in Sources */, B1C782322500F96700337EB9 /* SplitViewPresentable.swift in Sources */, B14F142F22144F6500FA0595 /* KeyBackupRecoverFromRecoveryKeyViewModelType.swift in Sources */, diff --git a/Riot/Managers/PushNotification/PushNotificationService.m b/Riot/Managers/PushNotification/PushNotificationService.m index 5058b1e16..824a68333 100644 --- a/Riot/Managers/PushNotification/PushNotificationService.m +++ b/Riot/Managers/PushNotification/PushNotificationService.m @@ -305,7 +305,7 @@ Matrix session observer used to detect new opened sessions. NSLog(@"[PushNotificationService] launchBackgroundSync"); MXWeakify(self); - [account backgroundSync:20000 success:^{ + [account backgroundSync:20000 success:^(MXSyncResponse *syncResponse) { // Sanity check MXStrongifyAndReturnIfNil(self); diff --git a/Riot/Modules/Application/LegacyAppDelegate.m b/Riot/Modules/Application/LegacyAppDelegate.m index 90073766c..ee3286608 100644 --- a/Riot/Modules/Application/LegacyAppDelegate.m +++ b/Riot/Modules/Application/LegacyAppDelegate.m @@ -605,7 +605,7 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni _isAppForeground = YES; - [self configurePinCodeScreenFor:application createIfRequired:NO]; +// [self configurePinCodeScreenFor:application createIfRequired:NO]; } - (void)applicationDidBecomeActive:(UIApplication *)application @@ -715,6 +715,13 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni NSArray *mxAccounts = [MXKAccountManager sharedManager].activeAccounts; for (MXKAccount *account in mxAccounts) { + SyncResponseFileStore *syncResponseStore = [[SyncResponseFileStore alloc] initWithCredentials:account.mxCredentials]; + MXSyncResponse *syncResponse = syncResponseStore.syncResponse; + if (syncResponse) + { + [account.mxSession handleSyncResponse:syncResponse]; + } + // Do not resume for now, to test we've really fetched the events [account resume]; } diff --git a/RiotNSE/MXSyncResponse.swift b/RiotNSE/MXSyncResponse.swift new file mode 100644 index 000000000..2240b86fc --- /dev/null +++ b/RiotNSE/MXSyncResponse.swift @@ -0,0 +1,26 @@ +// +// Copyright 2020 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation +import MatrixSDK + +extension MXSyncResponse { + + func update(with response: MXSyncResponse) { + // TODO: To be implemented + } + +} diff --git a/RiotNSE/NSEMemoryStore.swift b/RiotNSE/NSEMemoryStore.swift index a45864db2..ad8484824 100644 --- a/RiotNSE/NSEMemoryStore.swift +++ b/RiotNSE/NSEMemoryStore.swift @@ -121,10 +121,11 @@ class NSEMemoryStore: MXMemoryStore { override var syncFilterId: String? { get { - let filter = MXFilterJSONModel() - filter.room = MXRoomFilter() - filter.room.rooms = [] - return filter.jsonString() + return fileStore.syncFilterId +// let filter = MXFilterJSONModel() +// filter.room = MXRoomFilter() +// filter.room.rooms = [] +// return filter.jsonString() } set { // no-op } diff --git a/RiotNSE/NotificationService.swift b/RiotNSE/NotificationService.swift index ebc08f96f..c720e9dc0 100644 --- a/RiotNSE/NotificationService.swift +++ b/RiotNSE/NotificationService.swift @@ -29,6 +29,7 @@ class NotificationService: UNNotificationServiceExtension { /// Cached events. Keys are eventId's var cachedEvents: [String: MXEvent] = [:] static var mxSession: MXSession? + static var syncResponseStore: SyncResponseStore? var showDecryptedContentInNotifications: Bool { return RiotSettings.shared.showDecryptedContentInNotifications } @@ -100,17 +101,20 @@ class NotificationService: UNNotificationServiceExtension { } func setupLogger() { - if !NotificationService.isLoggerInitialized { - if isatty(STDERR_FILENO) == 0 { - MXLogger.setSubLogName("nse") - MXLogger.redirectNSLog(toFiles: true) - } - NotificationService.isLoggerInitialized = true - } +// if !NotificationService.isLoggerInitialized { +// if isatty(STDERR_FILENO) == 0 { +// MXLogger.setSubLogName("nse") +// MXLogger.redirectNSLog(toFiles: true) +// } +// NotificationService.isLoggerInitialized = true +// } } func setup(withRoomId roomId: String, eventId: String, completion: @escaping () -> Void) { if let userAccount = MXKAccountManager.shared()?.activeAccounts.first { + if NotificationService.syncResponseStore == nil { + NotificationService.syncResponseStore = SyncResponseFileStore(withCredentials: userAccount.mxCredentials) + } if NotificationService.mxSession == nil { let store = NSEMemoryStore(withCredentials: userAccount.mxCredentials) NotificationService.mxSession = MXSession(matrixRestClient: MXRestClient(credentials: userAccount.mxCredentials, unrecognizedCertificateHandler: nil)) @@ -216,32 +220,45 @@ class NotificationService: UNNotificationServiceExtension { // use cached event handleEncryption(forEvent: cachedEvent) } else { - // attempt to fetch the event - mxSession.event(withEventId: eventId, inRoom: roomId, success: { [weak self] (event) in - guard let self = self else { - NSLog("[NotificationService] fetchEvent: MXSession.event method returned too late successfully.") - return - } - - guard let event = event else { - NSLog("[NotificationService] fetchEvent: MXSession.event method returned successfully with no event.") - self.fallbackToBestAttemptContent(forEventId: eventId) - return - } - + // do not call the /event api and just check if the event exists in the store + if let event = mxSession.store.event(withEventId: eventId, inRoom: roomId) { + NSLog("[NotificationService] fetchEvent: We have the event in store.") // cache this event self.cachedEvents[eventId] = event // handle encryption for this event handleEncryption(forEvent: event) - }) { [weak self] (error) in - guard let self = self else { - NSLog("[NotificationService] fetchEvent: MXSession.event method returned too late with error: \(String(describing: error))") - return - } - NSLog("[NotificationService] fetchEvent: MXSession.event method returned error: \(String(describing: error))") - self.fallbackToBestAttemptContent(forEventId: eventId) + } else { + NSLog("[NotificationService] fetchEvent: We don't have the event in store. Launch a background sync to fetch it.") + self.launchBackgroundSync(forEventId: eventId, roomId: roomId) } +// +// // attempt to fetch the event +// mxSession.event(withEventId: eventId, inRoom: roomId, success: { [weak self] (event) in +// guard let self = self else { +// NSLog("[NotificationService] fetchEvent: MXSession.event method returned too late successfully.") +// return +// } +// +// guard let event = event else { +// NSLog("[NotificationService] fetchEvent: MXSession.event method returned successfully with no event.") +// self.fallbackToBestAttemptContent(forEventId: eventId) +// return +// } +// +// // cache this event +// self.cachedEvents[eventId] = event +// +// // handle encryption for this event +// handleEncryption(forEvent: event) +// }) { [weak self] (error) in +// guard let self = self else { +// NSLog("[NotificationService] fetchEvent: MXSession.event method returned too late with error: \(String(describing: error))") +// return +// } +// NSLog("[NotificationService] fetchEvent: MXSession.event method returned error: \(String(describing: error))") +// self.fallbackToBestAttemptContent(forEventId: eventId) +// } } } @@ -255,11 +272,19 @@ class NotificationService: UNNotificationServiceExtension { // launch an initial background sync mxSession.backgroundSync(withTimeout: 20, ignoreSessionState: true) { [weak self] (response) in switch response { - case .success: + case .success(let syncResponse): guard let self = self else { NSLog("[NotificationService] launchBackgroundSync: MXSession.initialBackgroundSync returned too late successfully") return } + + if let oldSyncResponse = NotificationService.syncResponseStore?.syncResponse { + oldSyncResponse.update(with: syncResponse) + NotificationService.syncResponseStore?.syncResponse = oldSyncResponse + } else { + NotificationService.syncResponseStore?.syncResponse = syncResponse + } + // do not allow to sync anymore self.fetchEvent(withEventId: eventId, roomId: roomId, allowSync: false) break diff --git a/RiotNSE/SyncResponseStore.swift b/RiotNSE/SyncResponseStore.swift new file mode 100644 index 000000000..d316baf8e --- /dev/null +++ b/RiotNSE/SyncResponseStore.swift @@ -0,0 +1,106 @@ +// +// Copyright 2020 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation +import MatrixSDK + +@objc protocol SyncResponseStore: NSObjectProtocol { + var syncResponse: MXSyncResponse? { get set } +} + +@objcMembers +class SyncResponseFileStore: NSObject { + + private enum SyncResponseFileStoreConstants { + static let folderNname = "SyncResponse" + static let fileName = "syncResponse.json" + static let fileEncoding: String.Encoding = .utf8 + static let fileOperationQueue: DispatchQueue = .global(qos: .default) + } + private var filePath: URL! + private var credentials: MXCredentials + + init(withCredentials credentials: MXCredentials) { + self.credentials = credentials + super.init() + setupFilePath() + } + + private func setupFilePath() { + guard let userId = credentials.userId else { + fatalError("Credentials must provide a user identifier") + } + var cachePath: URL! + + if let appGroupIdentifier = MXSDKOptions.sharedInstance().applicationGroupIdentifier { + cachePath = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroupIdentifier) + } else { + cachePath = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first + } + + filePath = cachePath + .appendingPathComponent(SyncResponseFileStoreConstants.folderNname) + .appendingPathComponent(userId) + .appendingPathComponent(SyncResponseFileStoreConstants.fileName) + + SyncResponseFileStoreConstants.fileOperationQueue.async { + try? FileManager.default.createDirectory(at: self.filePath.deletingLastPathComponent(), + withIntermediateDirectories: true, + attributes: nil) + } + } + + private func readSyncResponse() -> MXSyncResponse? { + var fileContents: String? + + SyncResponseFileStoreConstants.fileOperationQueue.sync { + fileContents = try? String(contentsOf: filePath, + encoding: SyncResponseFileStoreConstants.fileEncoding) + } + guard let jsonString = fileContents else { + return nil + } + guard let json = MXTools.deserialiseJSONString(jsonString) as? [AnyHashable: Any] else { + return nil + } + return MXSyncResponse(fromJSON: json) + } + + private func saveSyncResponse(_ syncResponse: MXSyncResponse?) { + guard let syncResponse = syncResponse else { + try? FileManager.default.removeItem(at: filePath) + return + } + SyncResponseFileStoreConstants.fileOperationQueue.async { + try? syncResponse.jsonString()?.write(to: self.filePath, + atomically: true, + encoding: SyncResponseFileStoreConstants.fileEncoding) + } + } + +} + +extension SyncResponseFileStore: SyncResponseStore { + + var syncResponse: MXSyncResponse? { + get { + return readSyncResponse() + } set { + saveSyncResponse(newValue) + } + } + +}