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
4 changes: 3 additions & 1 deletion app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -133,11 +133,13 @@ dependencies {
exclude group: 'com.android.support', module: 'support-compat'
exclude group: 'com.android.support', module: 'appcompat-v7'
exclude group: "com.onyx.android.sdk", module: "onyxsdk-geometry"
exclude group: 'com.tencent', module: 'mmkv'
}

implementation('com.onyx.android.sdk:onyxsdk-base:1.8.5') {
exclude group: 'com.android.support', module: 'support-compat'
exclude group: 'com.android.support', module: 'appcompat-v7'
exclude group: 'com.tencent', module: 'mmkv'
}

// Temporary (?) fix for https://github.com/gaborauth/toolsboox-android/issues/305
Expand All @@ -155,7 +157,7 @@ dependencies {
debugImplementation "androidx.compose.ui:ui-test-manifest:$compose_version"
implementation "androidx.compose.runtime:runtime-livedata:$compose_version"
implementation "androidx.compose.runtime:runtime:$compose_version"
implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.10.0'
implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.11.0'

implementation "androidx.navigation:navigation-compose:2.9.8"

Expand Down
20 changes: 13 additions & 7 deletions app/src/main/java/com/ethran/notable/sync/FolderSyncService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@ class FolderSyncService @Inject constructor(
) {
private val folderSerializer = FolderSerializer

suspend fun syncFolders(webdavClient: WebDAVClient): AppResult<Unit, DomainError> {
suspend fun syncFolders(
webdavClient: WebDAVClient,
uploadOnly: Boolean
): AppResult<Unit, DomainError> {
SyncLogger.i(TAG, "Syncing folders...")
val localFolders = appRepository.folderRepository.getAll()
val remotePath = SyncPaths.foldersFile()
Expand All @@ -40,12 +43,15 @@ class FolderSyncService @Inject constructor(
}

val mergedFolders = folderMap.values.toList()
for (folder in mergedFolders) {
val existing = appRepository.folderRepository.get(folder.id)
if (existing != null) {
appRepository.folderRepository.update(folder)
} else {
appRepository.folderRepository.create(folder)

if (!uploadOnly) {
for (folder in mergedFolders) {
val existing = appRepository.folderRepository.get(folder.id)
if (existing != null) {
appRepository.folderRepository.update(folder)
} else {
appRepository.folderRepository.create(folder)
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@ class NotebookReconciliationService @Inject constructor(
) {
private val logger = SyncLogger

suspend fun syncExistingNotebooks(webdavClient: WebDAVClient): AppResult<Set<String>, DomainError> {
suspend fun syncExistingNotebooks(
webdavClient: WebDAVClient,
uploadOnly: Boolean
): AppResult<Set<String>, DomainError> {
val localNotebooks = appRepository.bookRepository.getAll()
val preDownloadNotebookIds = localNotebooks.map { it.id }.toSet()
val total = localNotebooks.size
Expand All @@ -28,7 +31,7 @@ class NotebookReconciliationService @Inject constructor(
localNotebooks.forEachIndexed { i, notebook ->
reporter.beginItem(index = i + 1, total = total, name = notebook.title)
// Individual notebook sync failures are non-fatal for the whole process
syncNotebook(notebook.id, webdavClient).onError { error ->
syncNotebook(notebook.id, webdavClient, uploadOnly).onError { error ->
persistentError = persistentError?.let { it + error } ?: error
}
}
Expand All @@ -40,7 +43,8 @@ class NotebookReconciliationService @Inject constructor(

suspend fun syncNotebook(
notebookId: String,
webdavClient: WebDAVClient
webdavClient: WebDAVClient,
uploadOnly: Boolean
): AppResult<Unit, DomainError> {
logger.i(TAG, "Syncing notebook: $notebookId")

Expand Down Expand Up @@ -70,10 +74,16 @@ class NotebookReconciliationService @Inject constructor(
manifestIfMatch = remoteEtag
)

diffMs < -TIMESTAMP_TOLERANCE_MS -> notebookSyncService.downloadNotebook(
notebookId,
webdavClient
)
diffMs < -TIMESTAMP_TOLERANCE_MS -> {
if (uploadOnly) {
AppResult.Error(DomainError.SyncUploadOnlySkip(localNotebook.title))
} else {
notebookSyncService.downloadNotebook(
notebookId,
webdavClient
)
}
}

diffMs > TIMESTAMP_TOLERANCE_MS -> notebookSyncService.uploadNotebook(
localNotebook,
Expand Down
73 changes: 57 additions & 16 deletions app/src/main/java/com/ethran/notable/sync/SyncOrchestrator.kt
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ class SyncOrchestrator @Inject constructor(
reporter.beginStep(SyncStep.INITIALIZING, PROGRESS_INITIALIZING, "Initializing sync...")

val settings = kvProxy.getSyncSettings()
val uploadOnly = settings.uploadOnly
var nonCriticalError: DomainError? = null

if (!settings.syncEnabled) {
val error = DomainError.SyncConfigError
Expand Down Expand Up @@ -91,7 +93,7 @@ class SyncOrchestrator @Inject constructor(
PROGRESS_SYNCING_FOLDERS,
"Syncing folders..."
)
folderSyncService.syncFolders(client).onFailure { error ->
folderSyncService.syncFolders(client, uploadOnly).onFailure { error ->
reporter.finishError(error, false)
return@withContext AppResult.Error(error)
}
Expand All @@ -101,34 +103,52 @@ class SyncOrchestrator @Inject constructor(
PROGRESS_APPLYING_DELETIONS,
"Applying remote deletions..."
)
val tombstonedIds =
val tombstonedIds = if (uploadOnly) {
emptySet()
} else {
notebookSyncService.applyRemoteDeletions(client, TOMBSTONE_MAX_AGE_DAYS)
.onFailure { error ->
return@withContext AppResult.Error(error)
}
}

reporter.beginStep(
SyncStep.SYNCING_NOTEBOOKS,
PROGRESS_SYNCING_NOTEBOOKS,
"Syncing local notebooks..."
)
val preDownloadIds = notebookReconciliationService.syncExistingNotebooks(client)
.onFailure { error ->
return@withContext AppResult.Error(error)
val localIdsSnapshot = appRepository.bookRepository.getAll().map { it.id }.toSet()
val preDownloadIds = when (
val syncResult = notebookReconciliationService.syncExistingNotebooks(client, uploadOnly)
) {
is AppResult.Success -> syncResult.data
is AppResult.Error -> {
val error = syncResult.error
if (uploadOnly && error.isOnlyUploadSkip()) {
nonCriticalError = error
localIdsSnapshot
} else {
return@withContext AppResult.Error(error)
}
}
}

reporter.beginStep(
SyncStep.DOWNLOADING_NEW,
PROGRESS_DOWNLOADING_NEW,
"Downloading new notebooks..."
)
val downloadedCount = notebookSyncService.downloadNewNotebooks(
client,
tombstonedIds,
settings,
preDownloadIds
).onFailure { error ->
return@withContext AppResult.Error(error)
val downloadedCount = if (uploadOnly) {
0
} else {
notebookSyncService.downloadNewNotebooks(
client,
tombstonedIds,
settings,
preDownloadIds
).onFailure { error ->
return@withContext AppResult.Error(error)
}
}

reporter.beginStep(
Expand All @@ -152,9 +172,7 @@ class SyncOrchestrator @Inject constructor(
deletedCount,
System.currentTimeMillis() - startTime
)
reporter.finishSuccess(summary)

AppResult.Success(Unit)
finalizeSyncResult(reporter, summary, nonCriticalError)

} catch (e: Exception) {
val error = DomainError.SyncError(
Expand Down Expand Up @@ -188,7 +206,11 @@ class SyncOrchestrator @Inject constructor(
settings.username,
settings.password
)
return@withContext notebookReconciliationService.syncNotebook(notebookId, client)
return@withContext notebookReconciliationService.syncNotebook(
notebookId,
client,
settings.uploadOnly
)
}
AppResult.Success(Unit)
}
Expand Down Expand Up @@ -281,6 +303,25 @@ class SyncOrchestrator @Inject constructor(
}
}

internal fun finalizeSyncResult(
reporter: SyncProgressReporter,
summary: SyncSummary,
nonCriticalError: DomainError?
): AppResult<Unit, DomainError> {
if (nonCriticalError != null && nonCriticalError.isOnlyUploadSkip()) {
reporter.finishSuccess(summary)
return AppResult.Success(Unit)
}

if (nonCriticalError != null) {
reporter.finishError(nonCriticalError, false)
return AppResult.Error(nonCriticalError)
}

reporter.finishSuccess(summary)
return AppResult.Success(Unit)
}

@EntryPoint
@InstallIn(SingletonComponent::class)
interface SyncOrchestratorEntryPoint {
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/java/com/ethran/notable/sync/SyncSettings.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,6 @@ data class SyncSettings(
val lastSyncTime: Long? = null,
val syncOnNoteClose: Boolean = true,
val wifiOnly: Boolean = false,
val uploadOnly: Boolean = false,
val syncedNotebookIds: Set<String> = emptySet()
)

Loading
Loading