Added events batching from Rust part to prevent excessive exchange between both sides
This commit is contained in:
parent
7e6ccbe810
commit
b1dcdeb79b
5 changed files with 71 additions and 41 deletions
|
@ -1,11 +1,15 @@
|
|||
extern crate notify;
|
||||
|
||||
use std::fmt::Debug;
|
||||
use std::path::Path;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::mpsc::{channel, RecvTimeoutError};
|
||||
use std::time::Duration;
|
||||
use std::time::{Duration, SystemTime, UNIX_EPOCH};
|
||||
use notify::{Config, Error, ErrorKind, Event, RecommendedWatcher, RecursiveMode, Watcher};
|
||||
|
||||
const MIN_TIME_IN_MS_BETWEEN_REFRESHES: u128 = 500;
|
||||
const WATCH_TIMEOUT: u64 = 500;
|
||||
|
||||
|
||||
pub fn watch_directory(
|
||||
path: String,
|
||||
notifier: Box<dyn WatchDirectoryNotifier>,
|
||||
|
@ -27,11 +31,49 @@ pub fn watch_directory(
|
|||
.watch(Path::new(path.as_str()), RecursiveMode::Recursive)
|
||||
.map_err(|err| err.kind.into_watcher_init_error())?;
|
||||
|
||||
let mut paths_cached: Vec<String> = Vec::new();
|
||||
|
||||
let mut last_update: u128 = 0;
|
||||
|
||||
while notifier.should_keep_looping() {
|
||||
match rx.recv_timeout(Duration::from_secs(1)) {
|
||||
let current_time = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.expect("We need a TARDIS to fix this")
|
||||
.as_millis();
|
||||
|
||||
// Updates are batched to prevent excessive communication between Kotlin and Rust, as the
|
||||
// bridge has overhead
|
||||
if last_update != 0 && current_time > (last_update + MIN_TIME_IN_MS_BETWEEN_REFRESHES) {
|
||||
last_update = 0;
|
||||
|
||||
if paths_cached.len() == 1 {
|
||||
let first_path = paths_cached.first().unwrap();
|
||||
let is_dir = PathBuf::from(first_path).is_dir();
|
||||
|
||||
if is_dir {
|
||||
println!("Ignored path cached {first_path} because it is a dir");
|
||||
} else {
|
||||
println!("Sending single file event to Kotlin side");
|
||||
notifier.detected_change(paths_cached.to_vec());
|
||||
}
|
||||
} else {
|
||||
println!("Sending batched events to Kotlin side");
|
||||
notifier.detected_change(paths_cached.to_vec());
|
||||
}
|
||||
|
||||
paths_cached.clear();
|
||||
}
|
||||
|
||||
match rx.recv_timeout(Duration::from_millis(WATCH_TIMEOUT)) {
|
||||
Ok(e) => {
|
||||
if let Some(paths) = get_paths_from_event_result(&e) {
|
||||
notifier.detected_change(paths)
|
||||
if let Some(mut paths) = get_paths_from_event_result(&e) {
|
||||
paths_cached.append(&mut paths);
|
||||
|
||||
last_update = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.expect("We need a TARDIS to fix this")
|
||||
.as_millis();
|
||||
println!("Event: {e:?}");
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
|
|
|
@ -22,8 +22,8 @@ object AppConstants {
|
|||
const val APP_NAME = "Gitnuro"
|
||||
const val APP_DESCRIPTION =
|
||||
"Gitnuro is a Git client that allows you to manage multiple repositories with a modern experience and live visual representation of your repositories' state."
|
||||
const val APP_VERSION = "1.3.0"
|
||||
const val APP_VERSION_CODE = 10
|
||||
const val APP_VERSION = "1.3.1"
|
||||
const val APP_VERSION_CODE = 11
|
||||
const val VERSION_CHECK_URL = "https://raw.githubusercontent.com/JetpackDuba/Gitnuro/main/latest.json"
|
||||
}
|
||||
|
||||
|
|
|
@ -59,7 +59,10 @@ class FileChangesWatcher @Inject constructor(
|
|||
// JGit may create .probe-UUID files for its internal stuff, we should not care about it
|
||||
val onlyProbeFiles = paths.all { it.contains("$systemSeparator.git$systemSeparator.probe-") }
|
||||
|
||||
matchesAnyIgnoreRule || isGitIgnoredFile || onlyProbeFiles
|
||||
// Ignore it if the change is the directory itself
|
||||
val isGitDir = paths.count() == 1 && paths.first() == "$pathStr$systemSeparator.git$systemSeparator"
|
||||
|
||||
matchesAnyIgnoreRule || isGitIgnoredFile || onlyProbeFiles || isGitDir
|
||||
}
|
||||
|
||||
val hasGitDirChanged = paths.any { it.startsWith("$pathStr$systemSeparator.git$systemSeparator") }
|
||||
|
|
|
@ -90,8 +90,6 @@ class TabState @Inject constructor(
|
|||
var refreshEvenIfCrashesInteractiveResult = false
|
||||
operationRunning = true
|
||||
|
||||
lastOperation = System.currentTimeMillis()
|
||||
|
||||
val processingInfo: ProcessingInfo = object : ProcessingInfo {
|
||||
override fun changeSubtitle(newSubtitle: String) {
|
||||
_processing.update { processingState ->
|
||||
|
@ -152,6 +150,7 @@ class TabState @Inject constructor(
|
|||
} finally {
|
||||
_processing.value = ProcessingState.None
|
||||
operationRunning = false
|
||||
lastOperation = System.currentTimeMillis()
|
||||
|
||||
if (refreshType != RefreshType.NONE && (!hasProcessFailed || refreshEvenIfCrashes || refreshEvenIfCrashesInteractiveResult)) {
|
||||
_refreshData.emit(refreshType)
|
||||
|
@ -217,7 +216,6 @@ class TabState @Inject constructor(
|
|||
var hasProcessFailed = false
|
||||
|
||||
operationRunning = true
|
||||
lastOperation = System.currentTimeMillis()
|
||||
|
||||
try {
|
||||
block(git)
|
||||
|
@ -235,6 +233,7 @@ class TabState @Inject constructor(
|
|||
_refreshData.emit(refreshType)
|
||||
|
||||
operationRunning = false
|
||||
lastOperation = System.currentTimeMillis()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -40,7 +40,6 @@ import java.io.File
|
|||
import javax.inject.Inject
|
||||
import javax.inject.Provider
|
||||
|
||||
private const val MIN_TIME_IN_MS_BETWEEN_REFRESHES = 1000L
|
||||
private const val MIN_TIME_AFTER_GIT_OPERATION = 2000L
|
||||
|
||||
private const val TAG = "TabViewModel"
|
||||
|
@ -202,10 +201,14 @@ class TabViewModel @Inject constructor(
|
|||
_showAuthorInfo.value = false
|
||||
authorViewModel = null
|
||||
}
|
||||
/**
|
||||
* 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" or even full refreshes.
|
||||
*
|
||||
*/
|
||||
|
||||
private suspend fun watchRepositoryChanges(git: Git) = tabScope.launch(Dispatchers.IO) {
|
||||
var asyncJob: Job? = null
|
||||
var lastNotify = 0L
|
||||
var hasGitDirChanged = false
|
||||
|
||||
launch {
|
||||
|
@ -213,15 +216,6 @@ class TabViewModel @Inject constructor(
|
|||
if (!tabState.operationRunning) { // Only update if there isn't any process running
|
||||
printDebug(TAG, "Detected changes in the repository's directory")
|
||||
|
||||
if (latestUpdateChangedGitDir) {
|
||||
hasGitDirChanged = true
|
||||
}
|
||||
|
||||
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()
|
||||
|
||||
if (currentTimeMillis - tabState.lastOperation < MIN_TIME_AFTER_GIT_OPERATION) {
|
||||
|
@ -229,25 +223,17 @@ class TabViewModel @Inject constructor(
|
|||
return@collect
|
||||
}
|
||||
|
||||
val diffTime = currentTimeMillis - lastNotify
|
||||
|
||||
// When .git dir has changed, do the refresh with a delay to avoid doing operations while a git
|
||||
// operation may be running
|
||||
if (diffTime > MIN_TIME_IN_MS_BETWEEN_REFRESHES && !hasGitDirChanged) {
|
||||
updateApp(false)
|
||||
printDebug(TAG, "Sync emit with diff time $diffTime")
|
||||
} else {
|
||||
asyncJob = async {
|
||||
delay(MIN_TIME_IN_MS_BETWEEN_REFRESHES)
|
||||
printDebug(TAG, "Async emit")
|
||||
if (isActive)
|
||||
updateApp(hasGitDirChanged)
|
||||
|
||||
hasGitDirChanged = false
|
||||
}
|
||||
if (latestUpdateChangedGitDir) {
|
||||
hasGitDirChanged = true
|
||||
}
|
||||
|
||||
lastNotify = currentTimeMillis
|
||||
if (isActive) {
|
||||
updateApp(hasGitDirChanged)
|
||||
}
|
||||
|
||||
hasGitDirChanged = false
|
||||
} else {
|
||||
printDebug(TAG, "Ignored file events during operation")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -267,7 +253,7 @@ class TabViewModel @Inject constructor(
|
|||
is WatcherInitException.WatchNotFound -> null // This should never trigger as we don't unwatch files
|
||||
}
|
||||
|
||||
if(message != null) {
|
||||
if (message != null) {
|
||||
errorsManager.addError(
|
||||
newErrorNow(
|
||||
exception = ex,
|
||||
|
|
Loading…
Reference in a new issue