From afc7d9df8eac79daa2abe9dd75be31ab0dc1288d Mon Sep 17 00:00:00 2001 From: Abdelilah El Aissaoui Date: Fri, 25 Feb 2022 00:40:11 +0100 Subject: [PATCH] Improved performance when multiple FS changes occur in a fraction of a second Between update there is a minimum of 0.5s even if there have been multiple file updates --- src/main/kotlin/app/git/DiffManager.kt | 2 +- src/main/kotlin/app/git/FileChangesWatcher.kt | 46 ++++++++++++++++--- .../kotlin/app/viewmodels/TabViewModel.kt | 20 ++++---- 3 files changed, 50 insertions(+), 18 deletions(-) diff --git a/src/main/kotlin/app/git/DiffManager.kt b/src/main/kotlin/app/git/DiffManager.kt index 3d69b83..ca20c53 100644 --- a/src/main/kotlin/app/git/DiffManager.kt +++ b/src/main/kotlin/app/git/DiffManager.kt @@ -87,7 +87,7 @@ class DiffManager @Inject constructor( } } -fun prepareTreeParser(repository: Repository, commit: RevCommit): AbstractTreeIterator? { +fun prepareTreeParser(repository: Repository, commit: RevCommit): AbstractTreeIterator { // from the commit we can build the tree which allows us to construct the TreeParser RevWalk(repository).use { walk -> val tree: RevTree = walk.parseTree(commit.tree.id) diff --git a/src/main/kotlin/app/git/FileChangesWatcher.kt b/src/main/kotlin/app/git/FileChangesWatcher.kt index f5db720..400968c 100644 --- a/src/main/kotlin/app/git/FileChangesWatcher.kt +++ b/src/main/kotlin/app/git/FileChangesWatcher.kt @@ -1,17 +1,26 @@ package app.git -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.flow.flow -import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.* +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.SharedFlow import java.io.IOException import java.nio.file.* import java.nio.file.StandardWatchEventKinds.* import java.nio.file.attribute.BasicFileAttributes import javax.inject.Inject +private const val MIN_TIME_IN_MS_BETWEEN_REFRESHES = 500L class FileChangesWatcher @Inject constructor() { - suspend fun watchDirectoryPath(pathStr: String, ignoredDirsPath: List) = flow { + private var lastNotify = 0L + private var asyncJob: Job? = null + + private val _changesNotifier = MutableSharedFlow() + val changesNotifier: SharedFlow = _changesNotifier + + suspend fun watchDirectoryPath(pathStr: String, ignoredDirsPath: List) = withContext(Dispatchers.IO) { + println(ignoredDirsPath) + val watchService = FileSystems.getDefault().newWatchService() val path = Paths.get(pathStr) @@ -40,10 +49,33 @@ class FileChangesWatcher @Inject constructor() { var key: WatchKey while (watchService.take().also { key = it } != null) { - this.emit(Unit) key.pollEvents() + + println("Polled events") + + asyncJob?.cancel() + + // Sometimes external apps can run filesystem multiple operations in a fraction of a second. + // To prevent excessive updates, we add a slight delay between updates emission to prevent slowing down + // the app by constantly running "git status". + val currentTimeMillis = System.currentTimeMillis() + val diffTime = currentTimeMillis - lastNotify + + if (diffTime > MIN_TIME_IN_MS_BETWEEN_REFRESHES) { + _changesNotifier.emit(currentTimeMillis) + println("Sync emit with diff time $diffTime") + } else { + asyncJob = async { + delay(MIN_TIME_IN_MS_BETWEEN_REFRESHES) + println("Async emit") + if (isActive) + _changesNotifier.emit(currentTimeMillis) + } + } + + lastNotify = currentTimeMillis + key.reset() } - }.flowOn(Dispatchers.IO) - + } } \ No newline at end of file diff --git a/src/main/kotlin/app/viewmodels/TabViewModel.kt b/src/main/kotlin/app/viewmodels/TabViewModel.kt index 50d11bf..33869a7 100644 --- a/src/main/kotlin/app/viewmodels/TabViewModel.kt +++ b/src/main/kotlin/app/viewmodels/TabViewModel.kt @@ -7,13 +7,10 @@ import app.credentials.CredentialsStateManager import app.git.* import app.newErrorNow import app.ui.SelectedItem -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.cancel +import kotlinx.coroutines.* import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.collect -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext import org.eclipse.jgit.api.Git import org.eclipse.jgit.lib.ObjectId import org.eclipse.jgit.lib.Repository @@ -148,15 +145,18 @@ class TabViewModel @Inject constructor( private suspend fun watchRepositoryChanges(git: Git) = tabState.managerScope.launch(Dispatchers.IO) { val ignored = git.status().call().ignoredNotInIndex.toList() + launch { + fileChangesWatcher.changesNotifier.collect { + if (!tabState.operationRunning) { // Only update if there isn't any process running + println("Changes detected, loading status") + checkUncommitedChanges() + } + } + } fileChangesWatcher.watchDirectoryPath( pathStr = git.repository.directory.parent, ignoredDirsPath = ignored, - ).collect { - if (!tabState.operationRunning) { // Only update if there isn't any process running - println("Changes detected, loading status") - checkUncommitedChanges() - } - } + ) } private suspend fun checkUncommitedChanges(fullUpdateLog: Boolean = false) = tabState.runOperation(