Skip to content

Commit

Permalink
File system monitoring
Browse files Browse the repository at this point in the history
  • Loading branch information
mdrlzy committed Oct 29, 2021
1 parent b61d525 commit 88c4f51
Show file tree
Hide file tree
Showing 18 changed files with 299 additions and 52 deletions.
2 changes: 2 additions & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ dependencies {
implementation "androidx.recyclerview:recyclerview:1.2.1"
implementation "androidx.documentfile:documentfile:1.0.1"

implementation 'com.jakewharton.timber:timber:5.0.1'

implementation "com.github.moxy-community:moxy:2.1.2"
implementation "com.github.moxy-community:moxy-androidx:2.1.2"
implementation "com.github.moxy-community:moxy-ktx:2.1.2"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,13 @@ import android.util.Log
import space.taran.arknavigator.mvp.model.dao.Database
import dagger.Module
import dagger.Provides
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import space.taran.arknavigator.mvp.model.IndexCache
import space.taran.arknavigator.mvp.model.IndexingEngine
import space.taran.arknavigator.mvp.model.TagsCache
import space.taran.arknavigator.mvp.model.fsmonitoring.FSMonitoring
import space.taran.arknavigator.mvp.model.repo.FoldersRepo
import space.taran.arknavigator.mvp.model.repo.ResourcesIndexFactory
import javax.inject.Singleton
Expand Down Expand Up @@ -39,14 +43,27 @@ class RepoModule {
return TagsCache(indexCache)
}

@Singleton
@Provides
fun appCoroutineScope(): CoroutineScope {
return CoroutineScope(SupervisorJob() + Dispatchers.Default)
}

@Singleton
@Provides
fun fsMonitoring(indexCache: IndexCache, tagsCache: TagsCache, appScope: CoroutineScope): FSMonitoring {
return FSMonitoring(indexCache, tagsCache, appScope)
}

@Singleton
@Provides
fun indexingEngine(
foldersRepo: FoldersRepo,
indexCache: IndexCache,
tagsCache: TagsCache,
resourcesIndexFactory: ResourcesIndexFactory
resourcesIndexFactory: ResourcesIndexFactory,
fsMonitoring: FSMonitoring
): IndexingEngine {
return IndexingEngine(indexCache, tagsCache, foldersRepo, resourcesIndexFactory)
return IndexingEngine(indexCache, tagsCache, foldersRepo, resourcesIndexFactory, fsMonitoring)
}
}
45 changes: 36 additions & 9 deletions app/src/main/java/space/taran/arknavigator/mvp/model/IndexCache.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import space.taran.arknavigator.mvp.model.dao.ResourceId
import space.taran.arknavigator.mvp.model.repo.AggregatedResourcesIndex
import space.taran.arknavigator.mvp.model.repo.Difference
import space.taran.arknavigator.mvp.model.repo.PlainResourcesIndex
import java.nio.file.Path

Expand All @@ -15,21 +16,35 @@ class IndexCache {

suspend fun onIndexChange(root: Path, index: PlainResourcesIndex) {
indexByRoot[root] = index
aggregatedIndex = AggregatedResourcesIndex(indexByRoot.values)
val affectedRootAndFavs = flowByRootAndFav.keys.filter { it.root == root }
affectedRootAndFavs.forEach {
flowByRootAndFav[it]!!.emit(indexByRoot[root]!!.listIds(it.fav))
}
emitChangesToAffectedRootAndFav(root, index)
}

suspend fun onResourceCreated(root: Path, resourcePath: Path) {
val index = indexByRoot[root]!!
index.reindexRoot(Difference(emptyList(), emptyList(), listOf(resourcePath)))
emitChangesToAffectedRootAndFav(root, index)
}

suspend fun onResourceDeleted(root: Path, resourcePath: Path): ResourceId {
val index = indexByRoot[root]!!
val id = index.metaByPath[resourcePath]!!.id
index.remove(id)
emitChangesToAffectedRootAndFav(root, indexByRoot[root]!!)
return id
}

suspend fun onResourceModified(root: Path, resourcePath: Path): PlainResourcesIndex {
val index = indexByRoot[root]!!
index.reindexRoot(Difference(emptyList(), listOf(resourcePath), emptyList()))
emitChangesToAffectedRootAndFav(root, index)
return index
}

suspend fun onReindexFinish() {
allRootsFlow.emit(aggregatedIndex.listAllIds())
}

fun remove(resourceId: ResourceId): Path? {
val path = aggregatedIndex.remove(resourceId)
return path
}
fun getIndexByRoot(root: Path) = indexByRoot[root]!!

fun listenResourcesChanges(rootAndFav: RootAndFav): StateFlow<Set<ResourceId>?> {
return if (rootAndFav.isAllRoots()) {
Expand All @@ -56,4 +71,16 @@ class IndexCache {
fun getPath(resourceId: ResourceId): Path? {
return aggregatedIndex.getPath(resourceId)
}

private suspend fun emitChangesToAffectedRootAndFav(root: Path, index: PlainResourcesIndex) {
aggregatedIndex = AggregatedResourcesIndex(indexByRoot.values)
if (allRootsFlow.value != null)
allRootsFlow.emit(aggregatedIndex.listAllIds())

val affectedRootAndFavs = flowByRootAndFav.keys.filter { it.root == root }
affectedRootAndFavs.forEach {
if (flowByRootAndFav[it]!!.value != index.listIds(it.fav))
flowByRootAndFav[it]!!.emit(index.listIds(it.fav))
}
}
}
Original file line number Diff line number Diff line change
@@ -1,38 +1,35 @@
package space.taran.arknavigator.mvp.model

import space.taran.arknavigator.mvp.model.dao.ResourceId
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import space.taran.arknavigator.mvp.model.fsmonitoring.FSMonitoring
import space.taran.arknavigator.mvp.model.repo.FoldersRepo
import space.taran.arknavigator.mvp.model.repo.ResourcesIndexFactory
import java.nio.file.Files
import java.nio.file.Path

class IndexingEngine(
private val indexCache: IndexCache,
private val tagsCache: TagsCache,
private val foldersRepo: FoldersRepo,
private val resourcesIndexFactory: ResourcesIndexFactory
private val resourcesIndexFactory: ResourcesIndexFactory,
private val fsMonitoring: FSMonitoring
) {
suspend fun reindex() {
suspend fun reindex() = withContext(Dispatchers.Default) {
val roots = foldersRepo.query().succeeded.keys
roots.forEach {
val index = resourcesIndexFactory.loadFromDatabase(it)
tagsCache.onIndexChanged(it, index)
indexCache.onIndexChange(it, index)
fsMonitoring.startWatchingRoot(it.toString())
}
indexCache.onReindexFinish()
tagsCache.onReindexFinish()
}

suspend fun index(path: Path) {
val index = resourcesIndexFactory.buildFromFilesystem(path)
indexCache.onIndexChange(path, index)
tagsCache.onIndexChanged(path, index)
}

suspend fun remove(resourceId: ResourceId): Path? {
val path = indexCache.remove(resourceId)
tagsCache.remove(resourceId)
Files.delete(path)
return path
suspend fun index(root: Path) = withContext(Dispatchers.Default) {
val index = resourcesIndexFactory.buildFromFilesystem(root)
indexCache.onIndexChange(root, index)
tagsCache.onIndexChanged(root, index)
fsMonitoring.startWatchingRoot(root.toString())
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,15 @@ package space.taran.arknavigator.mvp.model
import java.nio.file.Path

data class RootAndFav (
var root: Path?,
var fav: Path?
val root: Path?,
val fav: Path?
) {
init {
if (root == null && fav != null)
throw AssertionError("Combination null root and not null fav isn't allowed")
}

fun isAllRoots(): Boolean {
return root == null && fav == null
return root == null
}
}
36 changes: 28 additions & 8 deletions app/src/main/java/space/taran/arknavigator/mvp/model/TagsCache.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package space.taran.arknavigator.mvp.model

import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.collect
import space.taran.arknavigator.mvp.model.dao.ResourceId
import space.taran.arknavigator.mvp.model.repo.AggregatedTagsStorage
import space.taran.arknavigator.mvp.model.repo.PlainResourcesIndex
Expand All @@ -21,11 +20,7 @@ class TagsCache(val indexCache: IndexCache) {
storageByRoot[root] = storage
val allIds = index.listAllIds()
storage.cleanup(allIds)
aggregatedTagsStorage = AggregatedTagsStorage(storageByRoot.values)
val affectedRootAndFavs = flowByRootAndFav.keys.filter { it.root == root }
affectedRootAndFavs.forEach {
flowByRootAndFav[it]!!.emit(storage.getTags(index.listIds(it.fav)))
}
emitChangesToAffectedRootAndFav(root, storage, index)
}

suspend fun onReindexFinish() {
Expand Down Expand Up @@ -81,7 +76,32 @@ class TagsCache(val indexCache: IndexCache) {
aggregatedTagsStorage.remove(resourceId)
}

suspend fun setTags(id: ResourceId, tags: Tags) {
aggregatedTagsStorage.setTags(id, tags)
suspend fun setTags(rootAndFav: RootAndFav, id: ResourceId, tags: Tags) {
if (rootAndFav.isAllRoots()) {
aggregatedTagsStorage.setTags(id, tags)
emitChangesToAllRootsFlow()
} else {
val root = rootAndFav.root!!
val storage = storageByRoot[root]!!
storage.setTags(id, tags)
emitChangesToAffectedRootAndFav(root, storage, indexCache.getIndexByRoot(root))
}
}

private suspend fun emitChangesToAllRootsFlow() {
aggregatedTagsStorage = AggregatedTagsStorage(storageByRoot.values)
if (allRootsFlow.value != null)
allRootsFlow.emit(aggregatedTagsStorage.getTags(indexCache.listIds(RootAndFav(null, null))))
}

private suspend fun emitChangesToAffectedRootAndFav(root: Path, storage: PlainTagsStorage, index: PlainResourcesIndex) {
emitChangesToAllRootsFlow()

val affectedRootAndFavs = flowByRootAndFav.keys.filter { it.root == root }
affectedRootAndFavs.forEach {
val tags = storage.getTags(index.listIds(it.fav))
if (flowByRootAndFav[it]!!.value != tags)
flowByRootAndFav[it]!!.emit(tags)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package space.taran.arknavigator.mvp.model.fsmonitoring

import android.os.Build
import android.os.FileObserver
import androidx.annotation.RequiresApi
import java.io.File

class DirectoryObserver: FileObserver {
@RequiresApi(Build.VERSION_CODES.Q)
constructor(directory: File): super(directory)
constructor(directory: String): super(directory)

private lateinit var recursiveObs: RecursiveDirectoryObserver
private lateinit var directory: String

override fun onEvent(event: Int, path: String?) {
val eventCode = event and ALL_EVENTS
val eventPath = path?.let { "$directory/$path" }
recursiveObs.onEvent(directory, eventCode, eventPath)
}

companion object {
fun create(recursiveObs: RecursiveDirectoryObserver, directory: String): DirectoryObserver {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
DirectoryObserver(File(directory)).also {
it.recursiveObs = recursiveObs
it.directory = directory
}
} else {
DirectoryObserver(directory).also {
it.recursiveObs = recursiveObs
it.directory = directory
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package space.taran.arknavigator.mvp.model.fsmonitoring

import android.os.FileObserver
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import space.taran.arknavigator.mvp.model.IndexCache
import space.taran.arknavigator.mvp.model.TagsCache
import space.taran.arknavigator.utils.isTagsStorage
import java.nio.file.Path

class FSMonitoring(
val indexCache: IndexCache,
val tagsCache: TagsCache,
val appScope: CoroutineScope
) {
private val rootObservers = mutableListOf<RecursiveDirectoryObserver>()

fun startWatchingRoot(root: String) {
val obs = RecursiveDirectoryObserver(this, root)
rootObservers.add(obs)
obs.startWatching()
}

fun onEvent(root: Path, directory: Path, event: Int, eventPath: Path?) {
when (event) {
FileObserver.CREATE -> {}
FileObserver.CLOSE_WRITE -> onCloseWrite(root, directory, eventPath!!)
FileObserver.DELETE -> onDelete(root, directory, eventPath!!)
FileObserver.MOVE_SELF -> {}
FileObserver.MOVED_FROM -> onMovedFrom(root, directory, eventPath!!)
FileObserver.MOVED_TO -> onMovedTo(root, directory, eventPath!!)
FileObserver.DELETE_SELF -> {}
}
}

private fun onCloseWrite(root: Path, directory: Path, eventPath: Path) = appScope.launch(Dispatchers.Default) {
if (!isTagsStorage(eventPath)) {
val index = indexCache.onResourceModified(root, eventPath)
tagsCache.onIndexChanged(root, index)
}
}

private fun onDelete(root: Path, directory: Path, eventPath: Path) = appScope.launch(Dispatchers.Default) {
val id = indexCache.onResourceDeleted(root, eventPath)
tagsCache.remove(id)
}

private fun onMovedFrom(root: Path, directory: Path, eventPath: Path) = appScope.launch(Dispatchers.Default) {
indexCache.onResourceDeleted(root, eventPath)
}

private fun onMovedTo(root: Path, directory: Path, eventPath: Path) = appScope.launch(Dispatchers.Default) {
indexCache.onResourceCreated(root, eventPath)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package space.taran.arknavigator.mvp.model.fsmonitoring

import space.taran.arknavigator.utils.FSEventLogger
import java.io.File
import java.util.*
import kotlin.io.path.Path

class RecursiveDirectoryObserver(
private val fsMonitoring: FSMonitoring,
private val root: String
) {
var directoryObservers: MutableList<DirectoryObserver> = mutableListOf()

fun startWatching() {
directoryObservers = mutableListOf()
val stack: Stack<String> = Stack()
stack.push(root)
while (!stack.empty()) {
val parent: String = stack.pop()
directoryObservers.add(DirectoryObserver.create(this, parent))
val path = File(parent)
val files: Array<File> = path.listFiles() ?: continue
for (i in files.indices) {
if (files[i].isDirectory && !files[i].name.equals(".")
&& !files[i].name.equals("..")
) {
stack.push(files[i].path)
}
}
}
for (i in directoryObservers.indices) directoryObservers[i].startWatching()
}

fun onEvent(directory: String, event: Int, eventPath: String?) {
FSEventLogger.log(directory, event, eventPath)
fsMonitoring.onEvent(Path(root), Path(directory), event, eventPath?.let { Path(it) })
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import moxy.presenterScope
import ru.terrakok.cicerone.Router
import space.taran.arknavigator.R
import space.taran.arknavigator.mvp.model.IndexingEngine
import space.taran.arknavigator.mvp.model.RootAndFav
import space.taran.arknavigator.mvp.model.repo.FoldersRepo
import space.taran.arknavigator.mvp.model.repo.ResourcesIndexFactory
import space.taran.arknavigator.mvp.presenter.adapter.folderstree.FoldersTreePresenter
Expand Down
Loading

0 comments on commit 88c4f51

Please sign in to comment.