Skip to content

Commit

Permalink
PP-747: Make bookmark syncing a purely client-side option (#266)
Browse files Browse the repository at this point in the history
* Make bookmark syncing a purely client-side option

Fix: https://ebce-lyrasis.atlassian.net/browse/PP-747

* Note changes
  • Loading branch information
io7m authored Nov 28, 2023
1 parent 310cabe commit bab18bb
Show file tree
Hide file tree
Showing 21 changed files with 35 additions and 1,455 deletions.
7 changes: 6 additions & 1 deletion README-CHANGES.xml
Original file line number Diff line number Diff line change
Expand Up @@ -415,7 +415,12 @@
<c:ticket id="PP-737"/>
</c:tickets>
</c:change>
<c:change date="2023-11-21T00:00:00+00:00" summary="Make account deletion icon visible.">
<c:change date="2023-11-21T15:15:20+00:00" summary="Bookmark syncing is now a purely client-side setting.">
<c:tickets>
<c:ticket id="PP-747"/>
</c:tickets>
</c:change>
<c:change date="2023-11-21T15:13:27+00:00" summary="Make account deletion icon visible.">
<c:tickets>
<c:ticket id="PP-738"/>
</c:tickets>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,4 +104,12 @@ interface AccountType : AccountReadableType {
}
}
}

/**
* @return `true` if the current account has the credentials and support for bookmark syncing
*/

fun isBookmarkSyncable(): Boolean {
return this.loginState.credentials?.annotationsURI != null
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,6 @@ import org.nypl.simplified.books.api.bookmark.Bookmark

sealed class BookmarkEvent {

/**
* The status of bookmark syncing changed.
*/

data class BookmarkSyncSettingChanged(
val accountID: AccountID,
val status: BookmarkSyncEnableStatus
) : BookmarkEvent()

/**
* Synchronizing bookmarks for the given account has started.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,29 +7,6 @@ import java.net.URI

interface BookmarkHTTPCallsType {

/**
* @return `true` if annotation syncing is enabled for the given account
*/

@Throws(IOException::class)
fun syncingIsEnabled(
account: AccountType,
settingsURI: URI,
credentials: AccountAuthenticationCredentials
): Boolean

/**
* Enable or disable annotation syncing for the given account.
*/

@Throws(IOException::class)
fun syncingEnable(
account: AccountType,
settingsURI: URI,
credentials: AccountAuthenticationCredentials,
enabled: Boolean
)

/**
* Retrieve the list of bookmarks for the given account. This call will fail
* with an exception if syncing is not enabled.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,23 +19,6 @@ interface BookmarkServiceUsableType {

val bookmarkEvents: Observable<BookmarkEvent>

/**
* Fetch the bookmark sync status for the given account.
*/

fun bookmarkSyncStatus(
accountID: AccountID
): BookmarkSyncEnableStatus

/**
* Enable/disable bookmark syncing on the server.
*/

fun bookmarkSyncEnable(
accountID: AccountID,
enabled: Boolean
): FluentFuture<BookmarkSyncEnableResult>

/**
* Sync the bookmarks for the given account.
*/
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
package org.nypl.simplified.bookmarks.internal

import com.fasterxml.jackson.databind.JsonNode
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.node.ObjectNode
import one.irradia.mime.api.MIMECompatibility
import one.irradia.mime.api.MIMEType
import org.librarysimplified.http.api.LSHTTPClientType
import org.librarysimplified.http.api.LSHTTPRequestBuilderType.Method.Delete
import org.librarysimplified.http.api.LSHTTPRequestBuilderType.Method.Post
import org.librarysimplified.http.api.LSHTTPRequestBuilderType.Method.Put
import org.librarysimplified.http.api.LSHTTPResponseStatus
import org.nypl.simplified.accounts.api.AccountAuthenticatedHTTP
import org.nypl.simplified.accounts.api.AccountAuthenticatedHTTP.addCredentialsToProperties
Expand All @@ -18,7 +15,6 @@ import org.nypl.simplified.accounts.database.api.AccountType
import org.nypl.simplified.bookmarks.api.BookmarkAnnotation
import org.nypl.simplified.bookmarks.api.BookmarkAnnotationsJSON
import org.nypl.simplified.bookmarks.api.BookmarkHTTPCallsType
import org.nypl.simplified.json.core.JSONParserUtilities
import org.slf4j.LoggerFactory
import java.io.ByteArrayInputStream
import java.io.IOException
Expand Down Expand Up @@ -139,67 +135,6 @@ class BHTTPCalls(
}
}

override fun syncingEnable(
account: AccountType,
settingsURI: URI,
credentials: AccountAuthenticationCredentials,
enabled: Boolean
) {
val data =
this.serializeSynchronizeEnableData(enabled)
val auth =
AccountAuthenticatedHTTP.createAuthorization(credentials)
val put =
Put(data, MIMEType("vnd.librarysimplified", "user-profile+json", mapOf()))
val request =
this.http.newRequest(settingsURI)
.setAuthorization(auth)
.addCredentialsToProperties(credentials)
.setMethod(put)
.build()

return request.execute().use { response ->
when (val status = response.status) {
is LSHTTPResponseStatus.Responded.OK -> {
account.updateBasicTokenCredentials(status.getAccessToken())
}
is LSHTTPResponseStatus.Responded.Error -> {
this.logAndFail(settingsURI, status)
}
is LSHTTPResponseStatus.Failed ->
throw status.exception
}
}
}

override fun syncingIsEnabled(
account: AccountType,
settingsURI: URI,
credentials: AccountAuthenticationCredentials
): Boolean {
val auth =
AccountAuthenticatedHTTP.createAuthorization(credentials)
val request =
this.http.newRequest(settingsURI)
.setAuthorization(auth)
.addCredentialsToProperties(credentials)
.build()

return request.execute().use { response ->
when (val status = response.status) {
is LSHTTPResponseStatus.Responded.OK -> {
account.updateBasicTokenCredentials(status.getAccessToken())
this.deserializeSyncingEnabledFromStream(status.bodyStream ?: emptyStream())
}
is LSHTTPResponseStatus.Responded.Error -> {
this.logAndFail(settingsURI, status)
}
is LSHTTPResponseStatus.Failed ->
throw status.exception
}
}
}

private fun <T> logAndFail(
uri: URI,
error: LSHTTPResponseStatus.Responded.Error
Expand All @@ -224,34 +159,4 @@ class BHTTPCalls(
)
return response.first.items
}

private fun deserializeSyncingEnabledFromStream(value: InputStream): Boolean {
return this.deserializeSyncingEnabledFromJSON(this.objectMapper.readTree(value))
}

private fun deserializeSyncingEnabledFromJSON(node: JsonNode): Boolean {
return this.deserializeSyncingEnabledFromJSONObject(JSONParserUtilities.checkObject(null, node))
}

private fun deserializeSyncingEnabledFromJSONObject(node: ObjectNode): Boolean {
val settings = JSONParserUtilities.getObjectOrNull(node, "settings")
return if (settings != null) {
if (settings.has("simplified:synchronize_annotations")) {
val text: String? = settings.get("simplified:synchronize_annotations").asText()
text == "true"
} else {
true
}
} else {
true
}
}

private fun serializeSynchronizeEnableData(enabled: Boolean): ByteArray {
val settingsNode = this.objectMapper.createObjectNode()
settingsNode.put("simplified:synchronize_annotations", enabled)
val node = this.objectMapper.createObjectNode()
node.put("settings", settingsNode)
return this.objectMapper.writeValueAsBytes(node)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,19 +23,13 @@ import org.nypl.simplified.bookmarks.api.BookmarkEvent
import org.nypl.simplified.bookmarks.api.BookmarkHTTPCallsType
import org.nypl.simplified.bookmarks.api.Bookmarks
import org.nypl.simplified.bookmarks.api.BookmarkServiceType
import org.nypl.simplified.bookmarks.api.BookmarkSyncEnableResult
import org.nypl.simplified.bookmarks.api.BookmarkSyncEnableResult.SYNC_DISABLED
import org.nypl.simplified.bookmarks.api.BookmarkSyncEnableResult.SYNC_ENABLE_NOT_SUPPORTED
import org.nypl.simplified.bookmarks.api.BookmarkSyncEnableResult.SYNC_ENABLED
import org.nypl.simplified.bookmarks.api.BookmarkSyncEnableStatus
import org.nypl.simplified.books.api.BookID
import org.nypl.simplified.books.api.bookmark.Bookmark
import org.nypl.simplified.profiles.api.ProfileEvent
import org.nypl.simplified.profiles.api.ProfileNoneCurrentException
import org.nypl.simplified.profiles.api.ProfileSelection
import org.nypl.simplified.profiles.controller.api.ProfilesControllerType
import org.slf4j.LoggerFactory
import java.util.Collections
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit

Expand All @@ -60,8 +54,6 @@ class BService(
LoggerFactory.getLogger(BService::class.java)
private val objectMapper =
ObjectMapper()
private val accountsSyncChanging =
Collections.synchronizedSet(hashSetOf<AccountID>())

init {
this.disposables.add(
Expand Down Expand Up @@ -130,10 +122,6 @@ class BService(
}
}

private fun onAccountEnabledSyncing() {
this.sync()
}

private fun onAccountLoggedIn() {
this.sync()
}
Expand All @@ -154,103 +142,6 @@ class BService(
override val bookmarkEvents: Observable<BookmarkEvent>
get() = this.bookmarkEventsOut

override fun bookmarkSyncStatus(
accountID: AccountID
): BookmarkSyncEnableStatus {
val profile =
this.profilesController.profileCurrent()
val syncable =
BSyncableAccount.ofAccount(profile.account(accountID))

if (syncable == null) {
this.logger.error("bookmarkSyncEnable: account does not support syncing")
return BookmarkSyncEnableStatus.Idle(accountID, SYNC_ENABLE_NOT_SUPPORTED)
}

val changing = this.accountsSyncChanging.contains(accountID)
if (changing) {
return BookmarkSyncEnableStatus.Changing(accountID)
}

return BookmarkSyncEnableStatus.Idle(
accountID = accountID,
status = if (syncable.account.preferences.bookmarkSyncingPermitted) {
SYNC_ENABLED
} else {
SYNC_DISABLED
}
)
}

override fun bookmarkSyncEnable(
accountID: AccountID,
enabled: Boolean
): FluentFuture<BookmarkSyncEnableResult> {
return try {
this.bookmarkEventsOut.onNext(
BookmarkEvent.BookmarkSyncSettingChanged(
accountID = accountID,
status = BookmarkSyncEnableStatus.Changing(accountID)
)
)

val profile =
this.profilesController.profileCurrent()
val syncable =
BSyncableAccount.ofAccount(profile.account(accountID))

if (syncable == null) {
this.logger.error("bookmarkSyncEnable: account does not support syncing")
val status = SYNC_ENABLE_NOT_SUPPORTED

this.bookmarkEventsOut.onNext(
BookmarkEvent.BookmarkSyncSettingChanged(
accountID = accountID,
status = BookmarkSyncEnableStatus.Idle(accountID, status)
)
)

return FluentFuture.from(Futures.immediateFuture(status))
}

this.accountsSyncChanging.add(accountID)

val opEnableSync =
BServiceOpEnableSync(
logger = this.logger,
accountsSyncChanging = this.accountsSyncChanging,
bookmarkEventsOut = this.bookmarkEventsOut,
httpCalls = this.httpCalls,
profile = profile,
syncableAccount = syncable,
enable = enabled
)

val opCheck =
BServiceOpCheckSyncStatusForProfile(
logger = this.logger,
httpCalls = this.httpCalls,
profile = profile
)

val enableFuture =
FluentFuture.from(this.executor.submit(opEnableSync))
.transform(
{ result ->
opCheck.call()
result
},
this.executor
)

enableFuture.addListener({ this.onAccountEnabledSyncing() }, this.executor)
enableFuture
} catch (e: ProfileNoneCurrentException) {
this.logger.error("bookmarkSyncEnable: no profile is current: ", e)
FluentFuture.from(Futures.immediateFailedFuture(e))
}
}

override fun bookmarkSyncAccount(
accountID: AccountID,
bookID: BookID
Expand Down
Loading

0 comments on commit bab18bb

Please sign in to comment.