diff --git a/Palace.xcodeproj/project.pbxproj b/Palace.xcodeproj/project.pbxproj index cd5a620b2..072d3dbd5 100644 --- a/Palace.xcodeproj/project.pbxproj +++ b/Palace.xcodeproj/project.pbxproj @@ -4782,7 +4782,7 @@ CODE_SIGN_IDENTITY = "Apple Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 225; + CURRENT_PROJECT_VERSION = 226; DEVELOPMENT_TEAM = 88CBA74T8K; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 88CBA74T8K; ENABLE_BITCODE = NO; @@ -4840,7 +4840,7 @@ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES_ERROR; CODE_SIGN_ENTITLEMENTS = Palace/SimplyE.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; - CURRENT_PROJECT_VERSION = 225; + CURRENT_PROJECT_VERSION = 226; DEVELOPMENT_TEAM = 88CBA74T8K; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 88CBA74T8K; ENABLE_BITCODE = NO; @@ -5024,7 +5024,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 225; + CURRENT_PROJECT_VERSION = 226; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 88CBA74T8K; ENABLE_BITCODE = NO; @@ -5084,7 +5084,7 @@ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES_ERROR; CODE_SIGN_ENTITLEMENTS = Palace/SimplyE.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; - CURRENT_PROJECT_VERSION = 225; + CURRENT_PROJECT_VERSION = 226; DEVELOPMENT_TEAM = 88CBA74T8K; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = 88CBA74T8K; ENABLE_BITCODE = NO; diff --git a/Palace/Accounts/Library/Account.swift b/Palace/Accounts/Library/Account.swift index 300fdf247..1561f6639 100644 --- a/Palace/Accounts/Library/Account.swift +++ b/Palace/Accounts/Library/Account.swift @@ -211,7 +211,7 @@ protocol AccountLogoDelegate: AnyObject { } var syncPermissionGranted:Bool { get { - return getAccountDictionaryKey(accountSyncEnabledKey) as? Bool ?? false + return getAccountDictionaryKey(accountSyncEnabledKey) as? Bool ?? true } set { setAccountDictionaryKey(accountSyncEnabledKey, toValue: newValue as AnyObject) diff --git a/Palace/Book/UI/TPPBookCellDelegate.m b/Palace/Book/UI/TPPBookCellDelegate.m index 9c73a4335..835742175 100644 --- a/Palace/Book/UI/TPPBookCellDelegate.m +++ b/Palace/Book/UI/TPPBookCellDelegate.m @@ -140,13 +140,6 @@ - (void)openBook:(TPPBook *)book completion:(void (^ _Nullable)(void))completion - (void)openEPUB:(TPPBook *)book { [[TPPRootTabBarController sharedController] presentBook:book]; - - [TPPAnnotations requestServerSyncStatusForAccount:[TPPUserAccount sharedAccount] completion:^(BOOL enableSync) { - if (enableSync == YES) { - Account *currentAccount = [[AccountsManager sharedInstance] currentAccount]; - currentAccount.details.syncPermissionGranted = enableSync; - } - }]; } - (void)openPDF:(TPPBook *)book { diff --git a/Palace/Network/TPPNetworkExecutor.swift b/Palace/Network/TPPNetworkExecutor.swift index 6c22b6b4b..001573021 100644 --- a/Palace/Network/TPPNetworkExecutor.swift +++ b/Palace/Network/TPPNetworkExecutor.swift @@ -78,7 +78,7 @@ extension TPPNetworkExecutor: TPPRequestExecuting { /// the network or from the cache. /// - Returns: The task issueing the given request. @discardableResult - func executeRequest(_ req: URLRequest, completion: @escaping (_: NYPLResult) -> Void) -> URLSessionDataTask? { + func executeRequest(_ req: URLRequest, completion: @escaping (_: NYPLResult) -> Void) -> URLSessionDataTask { let userAccount = TPPUserAccount.sharedAccount() if let authDefinition = userAccount.authDefinition, authDefinition.isSaml { @@ -87,13 +87,13 @@ extension TPPNetworkExecutor: TPPRequestExecuting { if userAccount.isTokenRefreshRequired() { handleTokenRefresh(for: req, completion: completion) - return nil + return URLSessionDataTask() } if req.hasRetried { let error = createErrorForRetryFailure() completion(NYPLResult.failure(error, nil)) - return nil + return URLSessionDataTask() } return performDataTask(with: req, completion: completion) diff --git a/Palace/Network/TPPRequestExecuting.swift b/Palace/Network/TPPRequestExecuting.swift index df6c2cce3..773bdf73e 100644 --- a/Palace/Network/TPPRequestExecuting.swift +++ b/Palace/Network/TPPRequestExecuting.swift @@ -19,7 +19,7 @@ protocol TPPRequestExecuting { /// - Returns: The task issueing the given request. @discardableResult func executeRequest(_ req: URLRequest, - completion: @escaping (_: NYPLResult) -> Void) -> URLSessionDataTask? + completion: @escaping (_: NYPLResult) -> Void) -> URLSessionDataTask var requestTimeout: TimeInterval {get} diff --git a/Palace/Reader2/Bookmarks/TPPAnnotations.swift b/Palace/Reader2/Bookmarks/TPPAnnotations.swift index 1dc235f2a..b42cd2d71 100644 --- a/Palace/Reader2/Bookmarks/TPPAnnotations.swift +++ b/Palace/Reader2/Bookmarks/TPPAnnotations.swift @@ -30,203 +30,6 @@ protocol AnnotationsManager { } @objcMembers final class TPPAnnotations: NSObject { - // MARK: - Sync Settings - - /// Shows (if needed) the opt-in flow for syncing the user bookmarks and - /// reading position on the server. - /// - /// This is implemented with an alert that is displayed once for the current - /// library once the user is signed in, i.e.: - /// - If the user has never seen it before, show it. - /// - If the user has seen it on one of their other devices, don't show it. - /// Opting in will attempt to enable on the server, with appropriate error handling. - /// - Note: This flow will be run only for the user account on the currently - /// selected library. Anything else will result in a no-op. - /// - Parameters: - /// - userAccount: the account to attempt to enable annotations-syncing on. - /// - completion: if a network request is actually performed, this block - /// is guaranteed to be called on the Main queue. Otherwise, this is called - /// either on the same thread the function was invoked on or on the main - /// thread. - class func requestServerSyncStatus(forAccount userAccount: TPPUserAccount, - completion: @escaping (_ enableSync: Bool) -> ()) { - - guard syncIsPossible(userAccount) else { - Log.debug(#file, "Account does not satisfy conditions for sync setting request.") - completion(false) - return - } - - let settings = TPPSettings.shared - - if (settings.userHasSeenFirstTimeSyncMessage == true && - AccountsManager.shared.currentAccount?.details?.syncPermissionGranted == false) { - completion(false) - return - } - - self.permissionUrlRequest { (initialized, syncIsPermitted) in - - if (initialized && syncIsPermitted) { - completion(true) - settings.userHasSeenFirstTimeSyncMessage = true; - Log.debug(#file, "Sync has already been enabled on the server. Enable here as well.") - return - } else if (!initialized && settings.userHasSeenFirstTimeSyncMessage == false) { - Log.debug(#file, "Sync has never been initialized for the patron. Showing UIAlertController flow.") - let title = "Palace Sync" - let message = "Enable sync to save your reading position and bookmarks to your other devices.\n\nYou can change this any time in Settings." - let alertController = UIAlertController.init(title: title, message: message, preferredStyle: .alert) - let notNowAction = UIAlertAction.init(title: "Not Now", style: .default, handler: { action in - completion(false) - settings.userHasSeenFirstTimeSyncMessage = true; - }) - let enableSyncAction = UIAlertAction.init(title: "Enable Sync", style: .default, handler: { action in - self.updateServerSyncSetting(toEnabled: true) { success in - completion(success) - settings.userHasSeenFirstTimeSyncMessage = true; - } - }) - alertController.addAction(notNowAction) - alertController.addAction(enableSyncAction) - alertController.preferredAction = enableSyncAction - TPPAlertUtils.presentFromViewControllerOrNil(alertController: alertController, viewController: nil, animated: true, completion: nil) - } else { - completion(false) - } - } - } - - /// Ask the server to enable Annotations on the current user account for the - /// currently selected library. Server will return null, true, or false. Null - /// assumes the user has never been introduced to the feature ("initialized"). - /// The closure expects "enabled" which is strictly to inform this single client - /// how to respond based on the server's info. - /// - Parameters: - /// - enabled: whether to enable annotation-syncing or not. - /// - completion: if a network request is actually performed, this block - /// is guaranteed to be called on the Main queue. Otherwise, this is called - /// on the same thread the function was invoked on. - class func updateServerSyncSetting(toEnabled enabled: Bool, completion:@escaping (Bool)->()) { - if (TPPUserAccount.sharedAccount().hasCredentials() && - AccountsManager.shared.currentAccount?.details?.supportsSimplyESync == true) { - guard let userProfileUrl = URL(string: AccountsManager.shared.currentAccount?.details?.userProfileUrl ?? "") else { - Log.error(#file, "Could not create user profile URL from string. Abandoning attempt to update sync setting.") - completion(false) - return - } - let parameters = ["settings": ["simplified:synchronize_annotations": enabled]] as [String : Any] - syncSettingUrlRequest(userProfileUrl, parameters, 20, { success in - if !success { - handleSyncSettingError() - } - completion(success) - }) - } - } - - - /// - Parameter successHandler: Called only if the request succeeds. - /// Always called on the main thread. - private class func permissionUrlRequest(successHandler: @escaping (_ initialized: Bool, _ syncIsPermitted: Bool) -> ()) { - - guard let userProfileUrl = URL(string: AccountsManager.shared.currentAccount?.details?.userProfileUrl ?? "") else { - Log.error(#file, "Failed to create user profile URL from string. Abandoning attempt to retrieve sync setting.") - return - } - - var request = TPPNetworkExecutor.shared.request(for: userProfileUrl) - request.timeoutInterval = 60 - - let dataTask = TPPNetworkExecutor.shared.GET(request: request) { (data, response, error) in - DispatchQueue.main.async { - - if let error = error as NSError? { - Log.error(#file, "Request Error Code: \(error.code). Description: \(error.localizedDescription)") - return - } - guard let data = data, - let response = (response as? HTTPURLResponse) else { - Log.error(#file, "No Data or No Server Response present after request.") - return - } - - if response.statusCode == 200 { - if let json = try? JSONSerialization.jsonObject(with: data, options: []) as! [String:Any], - let settings = json["settings"] as? [String:Any], - let syncSetting = settings["simplified:synchronize_annotations"] { - if syncSetting is NSNull { - successHandler(false, false) - } else { - successHandler(true, syncSetting as? Bool ?? false) - } - } else { - Log.error(#file, "Error parsing JSON or finding sync-setting key/value.") - } - } else { - Log.error(#file, "Server response returned error code: \(response.statusCode))") - } - } - } - dataTask?.resume() - } - - /// - parameter completion: if a network request is actually performed, this - /// is guaranteed to be called on the Main queue. Otherwise, this is called - /// on the same thread the function was invoked on. - private class func syncSettingUrlRequest(_ url: URL, - _ parameters: [String:Any], - _ timeout: Double?, - _ completion: @escaping (Bool)->()) { - guard let jsonData = try? JSONSerialization.data(withJSONObject: parameters, options: [.prettyPrinted]) else { - Log.error(#file, "Network request abandoned. Could not create JSON from given parameters.") - completion(false) - return - } - - var request = TPPNetworkExecutor.shared.request(for: url) - request.httpBody = jsonData - request.setValue("vnd.librarysimplified/user-profile+json", forHTTPHeaderField: "Content-Type") - if let timeout = timeout { - request.timeoutInterval = timeout - } - - let task = TPPNetworkExecutor.shared.PUT(request: request) { (data, response, error) in - DispatchQueue.main.async { - - if let error = error as NSError? { - Log.error(#file, "Request Error Code: \(error.code). Description: \(error.localizedDescription)") - if NetworkQueue.StatusCodes.contains(error.code) { - self.addToOfflineQueue(nil, url, parameters) - } - completion(false) - return - } - guard let statusCode = (response as? HTTPURLResponse)?.statusCode else { - Log.error(#file, "No response received from server") - completion(false) - return - } - - if statusCode == 200 { - completion(true) - } else { - Log.error(#file, "Server Response Error. Status Code: \(statusCode)") - completion(false) - } - } - } - task?.resume() - } - - class func handleSyncSettingError() { - let title = Strings.Error.syncSettingChangeErrorTitle - let message = Strings.Error.syncSettingsChangeErrorBody - let alert = UIAlertController.init(title: title, message: message, preferredStyle: .alert) - alert.addAction(UIAlertAction.init(title: Strings.Generic.ok, style: .default, handler: nil)) - TPPAlertUtils.presentFromViewControllerOrNil(alertController: alert, viewController: nil, animated: true, completion: nil) - } - // MARK: - Reading Position /// Reads the current reading position from the server, parses the response @@ -483,11 +286,6 @@ protocol AnnotationsManager { class func deleteBookmarks(_ bookmarks: [TPPReadiumBookmark]) { - if !syncIsPossibleAndPermitted() { - Log.debug(#file, "Account does not support sync or sync is disabled.") - return - } - for localBookmark in bookmarks { if let annotationID = localBookmark.annotationId { deleteBookmark(annotationId: annotationID) { success in @@ -506,7 +304,7 @@ protocol AnnotationsManager { if !syncIsPossibleAndPermitted() { Log.debug(#file, "Account does not support sync or sync is disabled.") - completionHandler(false) + completionHandler(true) return } diff --git a/Palace/Settings/TPPSettings.swift b/Palace/Settings/TPPSettings.swift index 832ac1501..018838b6d 100644 --- a/Palace/Settings/TPPSettings.swift +++ b/Palace/Settings/TPPSettings.swift @@ -90,15 +90,6 @@ import Foundation } } - var userHasSeenFirstTimeSyncMessage: Bool { - get { - UserDefaults.standard.bool(forKey: TPPSettings.userSeenFirstTimeSyncMessageKey) - } - set(b) { - UserDefaults.standard.set(b, forKey: TPPSettings.userSeenFirstTimeSyncMessageKey) - } - } - var useBetaLibraries: Bool { get { UserDefaults.standard.bool(forKey: TPPSettings.useBetaLibrariesKey) diff --git a/Palace/Settings/TPPSettingsAccountDetailViewController.m b/Palace/Settings/TPPSettingsAccountDetailViewController.m index dedecb6a9..37875b17c 100644 --- a/Palace/Settings/TPPSettingsAccountDetailViewController.m +++ b/Palace/Settings/TPPSettingsAccountDetailViewController.m @@ -332,7 +332,6 @@ - (void)setupViews [self setupTableData]; self.syncSwitch = [[UISwitch alloc] initWithFrame:CGRectZero]; - [self checkSyncPermissionForCurrentPatron]; } - (NSArray *) cellsForAuthMethod:(AccountDetailsAuthentication *)authenticationMethod { @@ -904,6 +903,8 @@ - (UITableViewCell *)tableView:(__attribute__((unused)) UITableView *)tableView initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil]; self.syncSwitch.on = self.selectedAccount.details.syncPermissionGranted; + self.syncSwitch.enabled = true; + cell.accessoryView = self.syncSwitch; [self.syncSwitch addTarget:self action:@selector(syncSwitchChanged:) forControlEvents:UIControlEventValueChanged]; cell.selectionStyle = UITableViewCellSelectionStyleNone; @@ -1195,7 +1196,6 @@ - (void)accountDidChange { [[NSOperationQueue mainQueue] addOperationWithBlock:^{ if(self.selectedUserAccount.hasCredentials) { - [self checkSyncPermissionForCurrentPatron]; self.usernameTextField.text = self.selectedUserAccount.barcode; self.usernameTextField.enabled = NO; self.usernameTextField.textColor = [UIColor grayColor]; @@ -1367,30 +1367,8 @@ - (void)confirmAgeChange:(void (^)(BOOL))completion - (void)syncSwitchChanged:(UISwitch*)sender { - const BOOL currentSwitchState = sender.on; - - if (sender.on) { - self.syncSwitch.enabled = NO; - } else { - self.syncSwitch.on = NO; - } - - __weak __auto_type weakSelf = self; - [self.businessLogic changeSyncPermissionTo:currentSwitchState - postServerSyncCompletion:^(BOOL success) { - weakSelf.syncSwitch.enabled = YES; - weakSelf.syncSwitch.on = success; - }]; -} - -- (void)checkSyncPermissionForCurrentPatron -{ - [self.businessLogic checkSyncPermissionWithPreWork:^{ - self.syncSwitch.enabled = NO; - } postWork:^(BOOL enableSync){ - self.syncSwitch.on = enableSync; - self.syncSwitch.enabled = YES; - }]; + self.syncSwitch.on = sender.on; + self.selectedAccount.details.syncPermissionGranted = sender.on; } #pragma mark - UIApplication callbacks diff --git a/Palace/Settings/TPPSettingsAdvancedViewController.swift b/Palace/Settings/TPPSettingsAdvancedViewController.swift index 01ca39360..565ec2e30 100644 --- a/Palace/Settings/TPPSettingsAdvancedViewController.swift +++ b/Palace/Settings/TPPSettingsAdvancedViewController.swift @@ -56,24 +56,8 @@ import UIKit } private func disableSync() { - //Disable UI while working - let alert = UIAlertController(title: nil, message: DisplayStrings.pleaseWait, preferredStyle: .alert) - let loadingIndicator = UIActivityIndicatorView(frame: CGRect(x: 10, y: 5, width: 50, height: 50)) - loadingIndicator.hidesWhenStopped = true - loadingIndicator.style = .medium - loadingIndicator.startAnimating(); - - alert.view.addSubview(loadingIndicator) - present(alert, animated: true, completion: nil) - - TPPAnnotations.updateServerSyncSetting(toEnabled: false, completion: { success in - self.dismiss(animated: true, completion: nil) - if (success) { - self.account.details?.syncPermissionGranted = false; - TPPSettings.shared.userHasSeenFirstTimeSyncMessage = false; - self.navigationController?.popViewController(animated: true) - } - }) + self.account.details?.syncPermissionGranted = false; + self.navigationController?.popViewController(animated: true) } // MARK: - UITableViewDataSource diff --git a/Palace/SignInLogic/TPPSignInBusinessLogic+BookmarkSyncing.swift b/Palace/SignInLogic/TPPSignInBusinessLogic+BookmarkSyncing.swift index 947bf47cd..004466a09 100644 --- a/Palace/SignInLogic/TPPSignInBusinessLogic+BookmarkSyncing.swift +++ b/Palace/SignInLogic/TPPSignInBusinessLogic+BookmarkSyncing.swift @@ -19,59 +19,4 @@ extension TPPSignInBusinessLogic { userAccount.hasCredentials() && libraryAccountID == libraryAccountsProvider.currentAccount?.uuid } - - /// Updates server sync setting for the currently selected library. - /// - Parameters: - /// - granted: Whether the user is granting sync permission or not. - /// - postServerSyncCompletion: Only run when granting sync permission. - @objc func changeSyncPermission(to granted: Bool, - postServerSyncCompletion: @escaping (Bool) -> Void) { - if granted { - // When granting, attempt to enable on the server. - TPPAnnotations.updateServerSyncSetting(toEnabled: true) { success in - self.libraryAccount?.details?.syncPermissionGranted = success - postServerSyncCompletion(success) - } - } else { - // When revoking, just ignore the server's annotations. - libraryAccount?.details?.syncPermissionGranted = false - } - } - - /// Checks with the annotations sync status with the server, adding logic - /// to make sure only one such requests is being executed at a time. - /// - Parameters: - /// - preWork: Any preparatory work to be done. This block is run - /// synchronously on the main thread. It's not run at all if a request is - /// already ongoing or if the current library doesn't support syncing. - /// - postWork: Any final work to be done. This block is run - /// on the main thread. It's not run at all if a request is - /// already ongoing or if the current library doesn't support syncing. - @objc func checkSyncPermission(preWork: () -> Void, - postWork: @escaping (_ enableSync: Bool) -> Void) { - guard let libraryDetails = libraryAccount?.details else { - return - } - - guard permissionsCheckLock.try(), libraryDetails.supportsSimplyESync else { - Log.debug(#file, "Skipping sync setting check. Request already in progress or sync not supported.") - return - } - - TPPMainThreadRun.sync { - preWork() - } - - TPPAnnotations.requestServerSyncStatus(forAccount: userAccount) { enableSync in - if enableSync { - libraryDetails.syncPermissionGranted = true - } - - TPPMainThreadRun.sync { - postWork(enableSync) - } - - self.permissionsCheckLock.unlock() - } - } } diff --git a/Palace/SignInLogic/TPPSignInBusinessLogic.swift b/Palace/SignInLogic/TPPSignInBusinessLogic.swift index fea590401..11d9b38dd 100644 --- a/Palace/SignInLogic/TPPSignInBusinessLogic.swift +++ b/Palace/SignInLogic/TPPSignInBusinessLogic.swift @@ -84,9 +84,6 @@ class TPPSignInBusinessLogic: NSObject, TPPSignedInStateProvider, TPPCurrentLibr self.samlHelper.businessLogic = self } - /// Lock for ensuring internal state consistency. - let permissionsCheckLock = NSLock() - /// Signing in and out may imply syncing the book registry. let bookRegistry: TPPBookRegistrySyncing diff --git a/PalaceTests/Mocks/NYPLNetworkExecutorMock.swift b/PalaceTests/Mocks/NYPLNetworkExecutorMock.swift index 3a4355eff..d0608b9e4 100644 --- a/PalaceTests/Mocks/NYPLNetworkExecutorMock.swift +++ b/PalaceTests/Mocks/NYPLNetworkExecutorMock.swift @@ -22,7 +22,7 @@ class TPPRequestExecutorMock: TPPRequestExecuting { } func executeRequest(_ req: URLRequest, - completion: @escaping (NYPLResult) -> Void) -> URLSessionDataTask? { + completion: @escaping (NYPLResult) -> Void) -> URLSessionDataTask { DispatchQueue.main.async { guard let url = req.url else { diff --git a/ios-audiobooktoolkit b/ios-audiobooktoolkit index 07117b962..fc125e668 160000 --- a/ios-audiobooktoolkit +++ b/ios-audiobooktoolkit @@ -1 +1 @@ -Subproject commit 07117b962bacbf5ccfe6762813757fec2dfd550b +Subproject commit fc125e66864de62621675fc6cfd6de4cf2d29abb