Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import com.namehillsoftware.handoff.promises.Promise
class StoredFileAccess(private val context: Context) : AccessStoredFiles {

override fun promiseStoredFile(storedFileId: Int): Promise<StoredFile?> =
promiseTableMessage<StoredFile?> {
promiseTableMessage {
RepositoryAccessHelper(context).use { repositoryAccessHelper ->
getStoredFile(repositoryAccessHelper, storedFileId)
}
Expand All @@ -32,7 +32,7 @@ class StoredFileAccess(private val context: Context) : AccessStoredFiles {
getStoredFileTask(libraryId, serviceFile)

override fun promiseAllStoredFiles(libraryId: LibraryId): Promise<Collection<StoredFile>> =
promiseTableMessage<Collection<StoredFile>> {
promiseTableMessage {
RepositoryAccessHelper(context).use { repositoryAccessHelper ->
repositoryAccessHelper.beginNonExclusiveTransaction().use {
repositoryAccessHelper
Expand All @@ -44,7 +44,7 @@ class StoredFileAccess(private val context: Context) : AccessStoredFiles {
}

override fun promiseDanglingFiles(): Promise<Collection<StoredFile>> =
promiseTableMessage<Collection<StoredFile>> {
promiseTableMessage {
RepositoryAccessHelper(context).use { helper ->
helper
.mapSql(
Expand All @@ -58,19 +58,19 @@ class StoredFileAccess(private val context: Context) : AccessStoredFiles {
}

override fun promiseNewStoredFile(libraryId: LibraryId, serviceFile: ServiceFile): Promise<StoredFile> =
promiseTableMessage<StoredFile> {
promiseTableMessage {
RepositoryAccessHelper(context).use {
it.createStoredFile(libraryId, serviceFile)
}
}

override fun promiseUpdatedStoredFile(storedFile: StoredFile): Promise<StoredFile> =
promiseTableMessage<StoredFile> {
promiseTableMessage {
RepositoryAccessHelper(context).use { it.update(tableName, storedFile) }
}

private fun getStoredFileTask(libraryId: LibraryId, serviceFile: ServiceFile): Promise<StoredFile?> =
promiseTableMessage<StoredFile?> {
promiseTableMessage {
RepositoryAccessHelper(context).use { repositoryAccessHelper ->
repositoryAccessHelper.getStoredFile(
libraryId,
Expand All @@ -80,7 +80,7 @@ class StoredFileAccess(private val context: Context) : AccessStoredFiles {
}

override fun promiseDownloadingFiles(): Promise<List<StoredFile>> =
promiseTableMessage<List<StoredFile>> {
promiseTableMessage {
RepositoryAccessHelper(context).use { repositoryAccessHelper ->
repositoryAccessHelper
.mapSql(
Expand Down Expand Up @@ -111,7 +111,7 @@ class StoredFileAccess(private val context: Context) : AccessStoredFiles {
insert(tableName, StoredFile(libraryId, serviceFile, null, true))

override fun deleteStoredFile(storedFile: StoredFile): Promise<Unit> =
promiseTableMessage<Unit> {
promiseTableMessage {
RepositoryAccessHelper(context).use { repositoryAccessHelper ->
try {
repositoryAccessHelper.beginTransaction().use { closeableTransaction ->
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.lasthopesoftware.bluewater.client.stored.library.items.files.updates

import android.database.sqlite.SQLiteConstraintException
import com.lasthopesoftware.bluewater.client.browsing.files.ServiceFile
import com.lasthopesoftware.bluewater.client.browsing.library.repository.LibraryId
import com.lasthopesoftware.bluewater.client.stored.library.items.files.repository.StoredFile
import com.lasthopesoftware.bluewater.shared.lazyLogger
import com.lasthopesoftware.policies.retries.RecursivePromiseRetryHandler
import com.namehillsoftware.handoff.promises.Promise
import java.util.concurrent.atomic.AtomicInteger

class RetryingStoredFileUpdate(
private val inner: UpdateStoredFiles
) : UpdateStoredFiles by inner {

companion object {
private val logger by lazyLogger<RetryingStoredFileUpdate>()
}

override fun promiseStoredFileUpdate(libraryId: LibraryId, serviceFile: ServiceFile): Promise<StoredFile> {
val attempts = AtomicInteger()
return RecursivePromiseRetryHandler.retryOnException { error ->
val attempt = attempts.incrementAndGet()
if (attempt <= 2 && (error == null || error is SQLiteConstraintException && (error.message?.startsWith("UNIQUE constraint failed")) ?: false)) {
if (attempt > 1) {
logger.warn(
"Failed to update stored file (libraryId=$libraryId, serviceFile=$serviceFile) due to unique constraint failure, retrying.",
error
)
}
inner.promiseStoredFileUpdate(libraryId, serviceFile)
} else {
Promise(error)
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ class StoredFileUpdater(
.keepPromise(storedFile)

val promisedLibrary = libraryProvider.promiseLibrary(libraryId)
return storedFileAccess.promiseStoredFile(libraryId, serviceFile)
return storedFileAccess
.promiseStoredFile(libraryId, serviceFile)
.eventually { storedFile ->
storedFile
?.toPromise()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package com.lasthopesoftware.bluewater.client.stored.library.items.files.updates.GivenATypicalLibrary.WithoutTheStoredFile.AndTheUniqueConstraintFailsOnce

import android.database.sqlite.SQLiteConstraintException
import com.lasthopesoftware.AndroidContext
import com.lasthopesoftware.bluewater.client.browsing.files.ServiceFile
import com.lasthopesoftware.bluewater.client.browsing.library.repository.LibraryId
import com.lasthopesoftware.bluewater.client.stored.library.items.files.repository.StoredFile
import com.lasthopesoftware.bluewater.client.stored.library.items.files.updates.RetryingStoredFileUpdate
import com.lasthopesoftware.bluewater.shared.promises.extensions.toExpiringFuture
import com.lasthopesoftware.promises.extensions.toPromise
import com.namehillsoftware.handoff.promises.Promise
import io.mockk.every
import io.mockk.mockk
import org.assertj.core.api.AssertionsForClassTypes.assertThat
import org.junit.Test
import java.util.concurrent.TimeUnit

class WhenUpdatingTheFile : AndroidContext() {

companion object {
private const val libraryId = 400
private const val serviceFileId = "dY10xa2sD"

private val sut by lazy {
RetryingStoredFileUpdate(
mockk {
every { promiseStoredFileUpdate(LibraryId(libraryId), ServiceFile(serviceFileId)) } returns
Promise(SQLiteConstraintException("UNIQUE constraint failed: StoredFiles.libraryId, StoredFiles.serviceId (code 2067 SQLITE_CONSTRAINT_UNIQUE)")) andThen
StoredFile().toPromise()
},
)
}
private var storedFile: StoredFile? = null
}

override fun before() {
storedFile = sut
.promiseStoredFileUpdate(LibraryId(libraryId), ServiceFile(serviceFileId))
.toExpiringFuture()
.get(1, TimeUnit.MINUTES)
}

@Test
fun `then the file is downloaded`() {
assertThat(storedFile).isNotNull
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package com.lasthopesoftware.bluewater.client.stored.library.items.files.updates.GivenATypicalLibrary.WithoutTheStoredFile.AndTheUniqueConstraintFailsTwice

import android.database.sqlite.SQLiteConstraintException
import com.lasthopesoftware.AndroidContext
import com.lasthopesoftware.bluewater.client.browsing.files.ServiceFile
import com.lasthopesoftware.bluewater.client.browsing.library.repository.LibraryId
import com.lasthopesoftware.bluewater.client.stored.library.items.files.repository.StoredFile
import com.lasthopesoftware.bluewater.client.stored.library.items.files.updates.RetryingStoredFileUpdate
import com.lasthopesoftware.bluewater.shared.promises.extensions.toExpiringFuture
import com.lasthopesoftware.promises.extensions.toPromise
import com.namehillsoftware.handoff.promises.Promise
import io.mockk.every
import io.mockk.mockk
import org.assertj.core.api.AssertionsForClassTypes.assertThat
import org.junit.Test
import java.util.concurrent.ExecutionException
import java.util.concurrent.TimeUnit

class WhenUpdatingTheFile : AndroidContext() {

companion object {
private const val libraryId = 809
private const val serviceFileId = "NJpjEijZ5Qz"

private val sut by lazy {

RetryingStoredFileUpdate(
mockk {
every { promiseStoredFileUpdate(LibraryId(libraryId), ServiceFile(serviceFileId)) } returns
Promise(SQLiteConstraintException("UNIQUE constraint failed: StoredFiles.libraryId, StoredFiles.serviceId (code 2067 SQLITE_CONSTRAINT_UNIQUE)")) andThen
Promise(SQLiteConstraintException("UNIQUE constraint failed: StoredFiles.libraryId, StoredFiles.serviceId (code 2067 SQLITE_CONSTRAINT_UNIQUE)")) andThen
StoredFile().toPromise()
},
)
}

private var exception: SQLiteConstraintException? = null
private var storedFile: StoredFile? = null
}

override fun before() {
try {
storedFile = sut
.promiseStoredFileUpdate(LibraryId(libraryId), ServiceFile(serviceFileId))
.toExpiringFuture()
.get(1, TimeUnit.MINUTES)
} catch (e: ExecutionException) {
exception = e.cause as? SQLiteConstraintException
}
}

@Test
fun `then the file is NOT downloaded`() {
assertThat(storedFile).isNull()
}

@Test
fun `then the exception is correct`() {
assertThat(exception?.message).isEqualTo("UNIQUE constraint failed: StoredFiles.libraryId, StoredFiles.serviceId (code 2067 SQLITE_CONSTRAINT_UNIQUE)")
}
}
Loading