diff --git a/Frameworks/Account/Account.swift b/Frameworks/Account/Account.swift index 0c025c1d1..4866c329a 100644 --- a/Frameworks/Account/Account.swift +++ b/Frameworks/Account/Account.swift @@ -341,10 +341,20 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container, self.delegate.refreshAll(for: self, completion: completion) } - public func syncArticleStatus(completion: (() -> Void)? = nil) { - delegate.sendArticleStatus(for: self) { [unowned self] in - self.delegate.refreshArticleStatus(for: self) { - completion?() + public func syncArticleStatus(completion: ((Result) -> Void)? = nil) { + delegate.sendArticleStatus(for: self) { [unowned self] result in + switch result { + case .success: + self.delegate.refreshArticleStatus(for: self) { result in + switch result { + case .success: + completion?(.success(())) + case .failure(let error): + completion?(.failure(error)) + } + } + case .failure(let error): + completion?(.failure(error)) } } } @@ -369,6 +379,11 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container, } + public func suspend() { + delegate.cancelAll(for: self) + save() + } + public func save() { metadataFile.save() feedMetadataFile.save() diff --git a/Frameworks/Account/AccountDelegate.swift b/Frameworks/Account/AccountDelegate.swift index 4a3978f10..de13bf9cb 100644 --- a/Frameworks/Account/AccountDelegate.swift +++ b/Frameworks/Account/AccountDelegate.swift @@ -22,9 +22,10 @@ protocol AccountDelegate { var refreshProgress: DownloadProgress { get } + func cancelAll(for account: Account) func refreshAll(for account: Account, completion: @escaping (Result) -> Void) - func sendArticleStatus(for account: Account, completion: @escaping (() -> Void)) - func refreshArticleStatus(for account: Account, completion: @escaping (() -> Void)) + func sendArticleStatus(for account: Account, completion: @escaping ((Result) -> Void)) + func refreshArticleStatus(for account: Account, completion: @escaping ((Result) -> Void)) func importOPML(for account:Account, opmlFile: URL, completion: @escaping (Result) -> Void) diff --git a/Frameworks/Account/AccountManager.swift b/Frameworks/Account/AccountManager.swift index 7c7ab7f1b..385b5e389 100644 --- a/Frameworks/Account/AccountManager.swift +++ b/Frameworks/Account/AccountManager.swift @@ -160,6 +160,10 @@ public final class AccountManager: UnreadCountProvider { return accountsDictionary[accountID] } + public func suspendAll() { + accounts.forEach { $0.suspend() } + } + public func refreshAll(errorHandler: @escaping (Error) -> Void, completion: (() ->Void)? = nil) { let group = DispatchGroup() @@ -187,7 +191,7 @@ public final class AccountManager: UnreadCountProvider { activeAccounts.forEach { group.enter() - $0.syncArticleStatus() { + $0.syncArticleStatus() { _ in group.leave() } } diff --git a/Frameworks/Account/AccountTests/TestTransport.swift b/Frameworks/Account/AccountTests/TestTransport.swift index 15f3635af..ba3a2d255 100644 --- a/Frameworks/Account/AccountTests/TestTransport.swift +++ b/Frameworks/Account/AccountTests/TestTransport.swift @@ -32,6 +32,8 @@ final class TestTransport: Transport { return HTTPURLResponse(url: url, statusCode: statusCode, httpVersion: "HTTP/1.1", headerFields: nil)! } + func cancelAll() { } + func send(request: URLRequest, completion: @escaping (Result<(HTTPURLResponse, Data?), Error>) -> Void) { guard let url = request.url, let components = URLComponents(url: url, resolvingAgainstBaseURL: false) else { diff --git a/Frameworks/Account/Feedbin/FeedbinAPICaller.swift b/Frameworks/Account/Feedbin/FeedbinAPICaller.swift index 271821d94..ddfe72292 100644 --- a/Frameworks/Account/Feedbin/FeedbinAPICaller.swift +++ b/Frameworks/Account/Feedbin/FeedbinAPICaller.swift @@ -42,6 +42,10 @@ final class FeedbinAPICaller: NSObject { self.transport = transport } + func cancelAll() { + transport.cancelAll() + } + func validateCredentials(completion: @escaping (Result) -> Void) { let callURL = feedbinBaseURL.appendingPathComponent("authentication.json") diff --git a/Frameworks/Account/Feedbin/FeedbinAccountDelegate.swift b/Frameworks/Account/Feedbin/FeedbinAccountDelegate.swift index 3aca53728..901b3b8de 100644 --- a/Frameworks/Account/Feedbin/FeedbinAccountDelegate.swift +++ b/Frameworks/Account/Feedbin/FeedbinAccountDelegate.swift @@ -15,6 +15,7 @@ import os.log public enum FeedbinAccountDelegateError: String, Error { case invalidParameter = "There was an invalid parameter passed." + case unknown = "An unknown error occurred." } final class FeedbinAccountDelegate: AccountDelegate { @@ -72,6 +73,10 @@ final class FeedbinAccountDelegate: AccountDelegate { var refreshProgress = DownloadProgress(numberOfTasks: 0) + func cancelAll(for account: Account) { + caller.cancelAll() + } + func refreshAll(for account: Account, completion: @escaping (Result) -> Void) { retrieveCredentialsIfNecessary(account) @@ -80,16 +85,16 @@ final class FeedbinAccountDelegate: AccountDelegate { refreshAccount(account) { result in switch result { case .success(): - - self.sendArticleStatus(for: account) { - self.refreshArticleStatus(for: account) { - self.refreshArticles(account) { - self.refreshMissingArticles(account) { - self.refreshProgress.clear() - DispatchQueue.main.async { - completion(.success(())) - } - } + + self.refreshArticlesAndStatuses(account) { result in + switch result { + case .success(): + completion(.success(())) + case .failure(let error): + DispatchQueue.main.async { + self.refreshProgress.clear() + let wrappedError = AccountError.wrappedError(error: error, account: account) + completion(.failure(wrappedError)) } } } @@ -106,7 +111,7 @@ final class FeedbinAccountDelegate: AccountDelegate { } - func sendArticleStatus(for account: Account, completion: @escaping (() -> Void)) { + func sendArticleStatus(for account: Account, completion: @escaping ((Result) -> Void)) { retrieveCredentialsIfNecessary(account) os_log(.debug, log: log, "Sending article statuses...") @@ -118,41 +123,59 @@ final class FeedbinAccountDelegate: AccountDelegate { let deleteStarredStatuses = syncStatuses.filter { $0.key == ArticleStatus.Key.starred && $0.flag == false } let group = DispatchGroup() - + var errorOccurred = false + group.enter() - sendArticleStatuses(createUnreadStatuses, apiCall: caller.createUnreadEntries) { + sendArticleStatuses(createUnreadStatuses, apiCall: caller.createUnreadEntries) { result in group.leave() + if case .failure = result { + errorOccurred = true + } } group.enter() - sendArticleStatuses(deleteUnreadStatuses, apiCall: caller.deleteUnreadEntries) { + sendArticleStatuses(deleteUnreadStatuses, apiCall: caller.deleteUnreadEntries) { result in group.leave() + if case .failure = result { + errorOccurred = true + } } group.enter() - sendArticleStatuses(createStarredStatuses, apiCall: caller.createStarredEntries) { + sendArticleStatuses(createStarredStatuses, apiCall: caller.createStarredEntries) { result in group.leave() + if case .failure = result { + errorOccurred = true + } } group.enter() - sendArticleStatuses(deleteStarredStatuses, apiCall: caller.deleteStarredEntries) { + sendArticleStatuses(deleteStarredStatuses, apiCall: caller.deleteStarredEntries) { result in group.leave() + if case .failure = result { + errorOccurred = true + } } group.notify(queue: DispatchQueue.main) { os_log(.debug, log: self.log, "Done sending article statuses.") - completion() + if errorOccurred { + completion(.failure(FeedbinAccountDelegateError.unknown)) + } else { + completion(.success(())) + } } } - func refreshArticleStatus(for account: Account, completion: @escaping (() -> Void)) { + func refreshArticleStatus(for account: Account, completion: @escaping ((Result) -> Void)) { retrieveCredentialsIfNecessary(account) os_log(.debug, log: log, "Refreshing article statuses...") let group = DispatchGroup() - + var errorOccurred = false + group.enter() caller.retrieveUnreadEntries() { result in switch result { @@ -160,6 +183,7 @@ final class FeedbinAccountDelegate: AccountDelegate { self.syncArticleReadState(account: account, articleIDs: articleIDs) group.leave() case .failure(let error): + errorOccurred = true os_log(.info, log: self.log, "Retrieving unread entries failed: %@.", error.localizedDescription) group.leave() } @@ -173,6 +197,7 @@ final class FeedbinAccountDelegate: AccountDelegate { self.syncArticleStarredState(account: account, articleIDs: articleIDs) group.leave() case .failure(let error): + errorOccurred = true os_log(.info, log: self.log, "Retrieving starred entries failed: %@.", error.localizedDescription) group.leave() } @@ -181,7 +206,11 @@ final class FeedbinAccountDelegate: AccountDelegate { group.notify(queue: DispatchQueue.main) { os_log(.debug, log: self.log, "Done refreshing article statuses.") - completion() + if errorOccurred { + completion(.failure(FeedbinAccountDelegateError.unknown)) + } else { + completion(.success(())) + } } } @@ -515,7 +544,7 @@ final class FeedbinAccountDelegate: AccountDelegate { database.insertStatuses(syncStatuses) if database.selectPendingCount() > 100 { - sendArticleStatus(for: account) {} + sendArticleStatus(for: account) { _ in } } return account.update(articles, statusKey: statusKey, flag: flag) @@ -639,6 +668,49 @@ private extension FeedbinAccountDelegate { } + func refreshArticlesAndStatuses(_ account: Account, completion: @escaping (Result) -> Void) { + self.sendArticleStatus(for: account) { result in + switch result { + case .success: + + self.refreshArticleStatus(for: account) { result in + switch result { + case .success: + + self.refreshArticles(account) { result in + switch result { + case .success: + + self.refreshMissingArticles(account) { result in + switch result { + case .success: + + DispatchQueue.main.async { + self.refreshProgress.clear() + completion(.success(())) + } + + case .failure(let error): + completion(.failure(error)) + } + } + + case .failure(let error): + completion(.failure(error)) + } + } + + case .failure(let error): + completion(.failure(error)) + } + } + + case .failure(let error): + completion(.failure(error)) + } + } + } + // This function can be deleted if Feedbin updates their taggings.json service to // show a change when a tag is renamed. func forceExpireFolderFeedRelationship(_ account: Account, _ tags: [FeedbinTag]?) { @@ -843,14 +915,15 @@ private extension FeedbinAccountDelegate { func sendArticleStatuses(_ statuses: [SyncStatus], apiCall: ([Int], @escaping (Result) -> Void) -> Void, - completion: @escaping (() -> Void)) { + completion: @escaping ((Result) -> Void)) { guard !statuses.isEmpty else { - completion() + completion(.success(())) return } let group = DispatchGroup() + var errorOccurred = false let articleIDs = statuses.compactMap { Int($0.articleID) } let articleIDGroups = articleIDs.chunked(into: 1000) @@ -863,6 +936,7 @@ private extension FeedbinAccountDelegate { self.database.deleteSelectedForProcessing(articleIDGroup.map { String($0) } ) group.leave() case .failure(let error): + errorOccurred = true os_log(.error, log: self.log, "Article status sync call failed: %@.", error.localizedDescription) self.database.resetSelectedForProcessing(articleIDGroup.map { String($0) } ) group.leave() @@ -872,7 +946,11 @@ private extension FeedbinAccountDelegate { } group.notify(queue: DispatchQueue.main) { - completion() + if errorOccurred { + completion(.failure(FeedbinAccountDelegateError.unknown)) + } else { + completion(.success(())) + } } } @@ -973,15 +1051,38 @@ private extension FeedbinAccountDelegate { case .success(let (entries, page)): self.processEntries(account: account, entries: entries) { - self.refreshArticleStatus(for: account) { - self.refreshArticles(account, page: page, updateFetchDate: nil) { - self.refreshProgress.completeTask() - self.refreshMissingArticles(account) { - self.refreshProgress.completeTask() - DispatchQueue.main.async { - completion(.success(feed)) + self.refreshArticleStatus(for: account) { result in + switch result { + case .success: + + self.refreshArticles(account, page: page, updateFetchDate: nil) { result in + switch result { + case .success: + + self.refreshProgress.completeTask() + self.refreshMissingArticles(account) { result in + switch result { + case .success: + + self.refreshProgress.completeTask() + DispatchQueue.main.async { + completion(.success(feed)) + } + + case .failure(let error): + completion(.failure(error)) + } + + } + + case .failure(let error): + completion(.failure(error)) } + } + + case .failure(let error): + completion(.failure(error)) } } } @@ -994,7 +1095,7 @@ private extension FeedbinAccountDelegate { } - func refreshArticles(_ account: Account, completion: @escaping (() -> Void)) { + func refreshArticles(_ account: Account, completion: @escaping ((Result) -> Void)) { os_log(.debug, log: log, "Refreshing articles...") @@ -1010,25 +1111,30 @@ private extension FeedbinAccountDelegate { self.processEntries(account: account, entries: entries) { self.refreshProgress.completeTask() - self.refreshArticles(account, page: page, updateFetchDate: updateFetchDate) { + self.refreshArticles(account, page: page, updateFetchDate: updateFetchDate) { result in os_log(.debug, log: self.log, "Done refreshing articles.") - completion() + switch result { + case .success: + completion(.success(())) + case .failure(let error): + completion(.failure(error)) + } } } case .failure(let error): - os_log(.error, log: self.log, "Refresh articles failed: %@.", error.localizedDescription) - completion() + completion(.failure(error)) } } } - func refreshMissingArticles(_ account: Account, completion: @escaping (() -> Void)) { + func refreshMissingArticles(_ account: Account, completion: @escaping ((Result) -> Void)) { os_log(.debug, log: log, "Refreshing missing articles...") let group = DispatchGroup() + var errorOccurred = false let fetchedArticleIDs = account.fetchArticleIDsForStatusesWithoutArticles() let articleIDs = Array(fetchedArticleIDs) @@ -1046,6 +1152,7 @@ private extension FeedbinAccountDelegate { } case .failure(let error): + errorOccurred = true os_log(.error, log: self.log, "Refresh missing articles failed: %@.", error.localizedDescription) group.leave() } @@ -1055,16 +1162,20 @@ private extension FeedbinAccountDelegate { group.notify(queue: DispatchQueue.main) { self.refreshProgress.completeTask() os_log(.debug, log: self.log, "Done refreshing missing articles.") - completion() + if errorOccurred { + completion(.failure(FeedbinAccountDelegateError.unknown)) + } else { + completion(.success(())) + } } } - func refreshArticles(_ account: Account, page: String?, updateFetchDate: Date?, completion: @escaping (() -> Void)) { + func refreshArticles(_ account: Account, page: String?, updateFetchDate: Date?, completion: @escaping ((Result) -> Void)) { guard let page = page else { if let lastArticleFetch = updateFetchDate { self.accountMetadata?.lastArticleFetch = lastArticleFetch } - completion() + completion(.success(())) return } @@ -1079,8 +1190,7 @@ private extension FeedbinAccountDelegate { } case .failure(let error): - os_log(.error, log: self.log, "Refresh articles for additional pages failed: %@.", error.localizedDescription) - completion() + completion(.failure(error)) } } } diff --git a/Frameworks/Account/Feedly/FeedlyAPICaller.swift b/Frameworks/Account/Feedly/FeedlyAPICaller.swift index 52f3decee..24b4108d2 100644 --- a/Frameworks/Account/Feedly/FeedlyAPICaller.swift +++ b/Frameworks/Account/Feedly/FeedlyAPICaller.swift @@ -53,6 +53,10 @@ final class FeedlyAPICaller { return baseUrlComponents.host } + func cancelAll() { + transport.cancelAll() + } + func importOpml(_ opmlData: Data, completionHandler: @escaping (Result) -> ()) { guard let accessToken = credentials?.secret else { return DispatchQueue.main.async { diff --git a/Frameworks/Account/Feedly/FeedlyAccountDelegate.swift b/Frameworks/Account/Feedly/FeedlyAccountDelegate.swift index c1e26f442..4fc95fd47 100644 --- a/Frameworks/Account/Feedly/FeedlyAccountDelegate.swift +++ b/Frameworks/Account/Feedly/FeedlyAccountDelegate.swift @@ -78,6 +78,10 @@ final class FeedlyAccountDelegate: AccountDelegate { // MARK: Account API + func cancelAll(for account: Account) { + // TODO: Implement me please + } + func refreshAll(for account: Account, completion: @escaping (Result) -> Void) { assert(Thread.isMainThread) @@ -113,12 +117,12 @@ final class FeedlyAccountDelegate: AccountDelegate { OperationQueue.main.addOperation(operation) } - func sendArticleStatus(for account: Account, completion: @escaping (() -> Void)) { + func sendArticleStatus(for account: Account, completion: @escaping ((Result) -> Void)) { // Ensure remote articles have the same status as they do locally. let send = FeedlySendArticleStatusesOperation(database: database, service: caller, log: log) send.completionBlock = { DispatchQueue.main.async { - completion() + completion(.success(())) } } OperationQueue.main.addOperation(send) @@ -134,9 +138,9 @@ final class FeedlyAccountDelegate: AccountDelegate { /// /// - Parameter account: The account whose articles have a remote status. /// - Parameter completion: Call on the main queue. - func refreshArticleStatus(for account: Account, completion: @escaping (() -> Void)) { + func refreshArticleStatus(for account: Account, completion: @escaping ((Result) -> Void)) { guard let credentials = credentials else { - return completion() + return completion(.success(())) } let group = DispatchGroup() @@ -157,7 +161,7 @@ final class FeedlyAccountDelegate: AccountDelegate { } group.notify(queue: .main) { - completion() + completion(.success(())) } OperationQueue.main.addOperations([getUnread, getStarred], waitUntilFinished: false) @@ -448,7 +452,7 @@ final class FeedlyAccountDelegate: AccountDelegate { os_log(.debug, log: log, "Marking %@ as %@.", articles.map { $0.title }, syncStatuses) if database.selectPendingCount() > 100 { - sendArticleStatus(for: account) { } + sendArticleStatus(for: account) { _ in } } return account.update(articles, statusKey: statusKey, flag: flag) diff --git a/Frameworks/Account/LocalAccount/LocalAccountDelegate.swift b/Frameworks/Account/LocalAccount/LocalAccountDelegate.swift index 97cc2e2ec..c85ca2947 100644 --- a/Frameworks/Account/LocalAccount/LocalAccountDelegate.swift +++ b/Frameworks/Account/LocalAccount/LocalAccountDelegate.swift @@ -31,18 +31,22 @@ final class LocalAccountDelegate: AccountDelegate { return refresher.progress } + func cancelAll(for account: Account) { + // TODO: implement me + } + func refreshAll(for account: Account, completion: @escaping (Result) -> Void) { refresher.refreshFeeds(account.flattenedFeeds()) { completion(.success(())) } } - func sendArticleStatus(for account: Account, completion: @escaping (() -> Void)) { - completion() + func sendArticleStatus(for account: Account, completion: @escaping ((Result) -> Void)) { + completion(.success(())) } - func refreshArticleStatus(for account: Account, completion: @escaping (() -> Void)) { - completion() + func refreshArticleStatus(for account: Account, completion: @escaping ((Result) -> Void)) { + completion(.success(())) } func importOPML(for account:Account, opmlFile: URL, completion: @escaping (Result) -> Void) { diff --git a/Frameworks/Account/ReaderAPI/ReaderAPIAccountDelegate.swift b/Frameworks/Account/ReaderAPI/ReaderAPIAccountDelegate.swift index abb7757aa..4de3ff830 100644 --- a/Frameworks/Account/ReaderAPI/ReaderAPIAccountDelegate.swift +++ b/Frameworks/Account/ReaderAPI/ReaderAPIAccountDelegate.swift @@ -19,7 +19,7 @@ public enum ReaderAPIAccountDelegateError: String, Error { } final class ReaderAPIAccountDelegate: AccountDelegate { - + private let database: SyncDatabase private let caller: ReaderAPICaller @@ -79,6 +79,10 @@ final class ReaderAPIAccountDelegate: AccountDelegate { var refreshProgress = DownloadProgress(numberOfTasks: 0) + func cancelAll(for account: Account) { + caller.cancelAll() + } + func refreshAll(for account: Account, completion: @escaping (Result) -> Void) { refreshProgress.addToNumberOfTasksAndRemaining(6) @@ -87,8 +91,8 @@ final class ReaderAPIAccountDelegate: AccountDelegate { switch result { case .success(): - self.sendArticleStatus(for: account) { - self.refreshArticleStatus(for: account) { + self.sendArticleStatus(for: account) { _ in + self.refreshArticleStatus(for: account) { _ in self.refreshArticles(account) { self.refreshMissingArticles(account) { self.refreshProgress.clear() @@ -112,7 +116,7 @@ final class ReaderAPIAccountDelegate: AccountDelegate { } - func sendArticleStatus(for account: Account, completion: @escaping (() -> Void)) { + func sendArticleStatus(for account: Account, completion: @escaping ((Result) -> Void)) { os_log(.debug, log: log, "Sending article statuses...") @@ -146,12 +150,12 @@ final class ReaderAPIAccountDelegate: AccountDelegate { group.notify(queue: DispatchQueue.main) { os_log(.debug, log: self.log, "Done sending article statuses.") - completion() + completion(.success(())) } } - func refreshArticleStatus(for account: Account, completion: @escaping (() -> Void)) { + func refreshArticleStatus(for account: Account, completion: @escaping ((Result) -> Void)) { os_log(.debug, log: log, "Refreshing article statuses...") @@ -185,7 +189,7 @@ final class ReaderAPIAccountDelegate: AccountDelegate { group.notify(queue: DispatchQueue.main) { os_log(.debug, log: self.log, "Done refreshing article statuses.") - completion() + completion(.success(())) } } @@ -403,7 +407,7 @@ final class ReaderAPIAccountDelegate: AccountDelegate { database.insertStatuses(syncStatuses) if database.selectPendingCount() > 100 { - sendArticleStatus(for: account) {} + sendArticleStatus(for: account) { _ in } } return account.update(articles, statusKey: statusKey, flag: flag) @@ -755,7 +759,7 @@ private extension ReaderAPIAccountDelegate { case .success(let (entries, page)): self.processEntries(account: account, entries: entries) { - self.refreshArticleStatus(for: account) { + self.refreshArticleStatus(for: account) { _ in self.refreshArticles(account, page: page) { self.refreshMissingArticles(account) { DispatchQueue.main.async { diff --git a/Frameworks/Account/ReaderAPI/ReaderAPICaller.swift b/Frameworks/Account/ReaderAPI/ReaderAPICaller.swift index ba1234a81..1b9454856 100644 --- a/Frameworks/Account/ReaderAPI/ReaderAPICaller.swift +++ b/Frameworks/Account/ReaderAPI/ReaderAPICaller.swift @@ -78,6 +78,10 @@ final class ReaderAPICaller: NSObject { self.transport = transport } + func cancelAll() { + transport.cancelAll() + } + func validateCredentials(endpoint: URL, completion: @escaping (Result) -> Void) { guard let credentials = credentials else { completion(.failure(CredentialsError.incompleteCredentials)) diff --git a/iOS/AppDelegate.swift b/iOS/AppDelegate.swift index 3f7db82ce..75db8252b 100644 --- a/iOS/AppDelegate.swift +++ b/iOS/AppDelegate.swift @@ -109,6 +109,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD func applicationWillTerminate(_ application: UIApplication) { shuttingDown = true + AccountManager.shared.suspendAll() } // MARK: Notifications @@ -243,7 +244,7 @@ private extension AppDelegate { func waitForProgressToFinish() { let completeProcessing = { [unowned self] in - AccountManager.shared.saveAll() + AccountManager.shared.suspendAll() UIApplication.shared.endBackgroundTask(self.waitBackgroundUpdateTask) self.waitBackgroundUpdateTask = UIBackgroundTaskIdentifier.invalid } diff --git a/iOS/ErrorHandler.swift b/iOS/ErrorHandler.swift index 898928327..64957ad3b 100644 --- a/iOS/ErrorHandler.swift +++ b/iOS/ErrorHandler.swift @@ -16,7 +16,11 @@ struct ErrorHandler { public static func present(_ viewController: UIViewController) -> (Error) -> () { return { [weak viewController] error in - viewController?.presentError(error) + if UIApplication.shared.applicationState == .active { + viewController?.presentError(error) + } else { + ErrorHandler.log(error) + } } } diff --git a/submodules/RSWeb b/submodules/RSWeb index 1ea5f5ccf..262a22023 160000 --- a/submodules/RSWeb +++ b/submodules/RSWeb @@ -1 +1 @@ -Subproject commit 1ea5f5ccfc3646ffdf2891abbc5ea63e3d449def +Subproject commit 262a220230ef393edb7c003132b6cdc16a17cb5e