Replies: 3 comments
|
Hi, and thanks a lot for your interest in the project! Let me try to answer point by point: 1) Sources and references As a general rule, I prefer dynamic libraries to be written in C for Linux, C or C++ for Windows, and Objective-C for macOS — these languages produce the smallest binaries. That said, the rule isn't absolute: for Nucleus v2.0, which introduces a new backend, I'm relying on the Tao backend in Rust. So the rule is rather: if it's simple enough to implement, I prefer a rewrite in C/C++/Objective-C; otherwise, Rust is acceptable provided there's already a mature ecosystem. I haven't really looked into 2) Important contribution rule As you can see in the other modules, every bridge must use JNI — no JNA, no FFM. 3) Testing When automated e2e tests are feasible, they're obviously preferred. But for most Nucleus modules, writing this kind of test would be extremely complex. I'd rather add a tab in the demo app. Nucleus separates its modules in a very modular way, so once we've verified that something works, there's no real reason for a change in another module to affect it. When updating code, you do need to check the behavior on Windows, macOS, and Linux — sorry, it's still very manual for now! 4) First milestone I'd suggest a JNI binding of the Feel free to ask if you have any other questions! |
|
Thanks, that direction makes sense to me. I did some research around the watcher API shape and the existing native integration pattern in Nucleus, and I think the public API can stay fairly small while still leaving room for the native backend to use richer platform details internally. My current API proposal would look roughly like this: import java.nio.file.Path
import java.time.Duration
import kotlinx.coroutines.flow.Flow
data class FsWatchSource(
val root: Path,
val recursive: Boolean,
// Optional user-facing label for logs, UI, or grouping.
// Keep this as String instead of Any? to avoid the watcher strongly retaining arbitrary user objects.
val name: String? = null,
)
sealed interface FsWatchEvent {
// Identifies which watch registration produced this event.
// Null means the backend can only say that the whole watcher is no longer fully trustworthy.
val source: FsWatchSource?
// This should be part of the first public model because native watchers can lose,
// merge, or coalesce events. When true, callers should rescan the relevant source.
val needsRescan: Boolean
data class Created(
val path: Path,
override val source: FsWatchSource? = null,
val isDirectory: Boolean? = null,
override val needsRescan: Boolean = false,
) : FsWatchEvent
data class Modified(
val path: Path,
override val source: FsWatchSource? = null,
val isDirectory: Boolean? = null,
override val needsRescan: Boolean = false,
) : FsWatchEvent
data class Removed(
val path: Path,
override val source: FsWatchSource? = null,
val isDirectory: Boolean? = null,
override val needsRescan: Boolean = false,
) : FsWatchEvent
data class Moved(
val from: Path,
val to: Path,
override val source: FsWatchSource? = null,
val isDirectory: Boolean? = null,
override val needsRescan: Boolean = false,
) : FsWatchEvent
data class Accessed(
val path: Path,
override val source: FsWatchSource? = null,
val isDirectory: Boolean? = null,
override val needsRescan: Boolean = false,
) : FsWatchEvent
data class Overflow(
override val source: FsWatchSource? = null,
override val needsRescan: Boolean = true,
) : FsWatchEvent
data class Other(
val paths: List<Path> = emptyList(),
override val source: FsWatchSource? = null,
val isDirectory: Boolean? = null,
override val needsRescan: Boolean = false,
) : FsWatchEvent
}
sealed interface FsWatchBackendStrategy {
// Default strategy: use the recommended native watcher backend for the current platform.
data object Auto : FsWatchBackendStrategy
// Useful when users prefer a hard failure instead of silently falling back to polling.
data object NativeOnly : FsWatchBackendStrategy
// Polling is slower, but more conservative for network filesystems, WSL, containers,
// or special mount points.
data class Polling(
val interval: Duration = Duration.ofSeconds(2),
val compareContents: Boolean = false,
) : FsWatchBackendStrategy
}
data class FsWatcherConfig(
// Default false avoids unexpectedly expanding recursive watches through symlinks.
// It also reduces symlink-cycle, duplicate-event, and unclear-root-semantics issues.
// Users who explicitly want to watch symlink targets can opt in.
val followSymlinks: Boolean = false,
val backend: FsWatchBackendStrategy = FsWatchBackendStrategy.Auto,
)
data class FsWatchError(
val message: String,
val source: FsWatchSource? = null,
val recoverable: Boolean = false,
val cause: Throwable? = null,
)
class FsWatchException(
message: String,
cause: Throwable? = null,
) : RuntimeException(message, cause)
interface FsWatchRegistration : AutoCloseable {
val source: FsWatchSource
val active: Boolean
// Close only this watched root.
override fun close()
}
interface FsWatcher : AutoCloseable {
val registrations: Set<FsWatchSource>
// File system events produced by this watcher.
val events: Flow<FsWatchEvent>
// Asynchronous backend errors, separate from synchronous watch(...) failures.
val errors: Flow<FsWatchError>
// Return a registration instead of Boolean so callers get a clear lifecycle handle.
// Registration failure should throw FsWatchException with a useful message/cause.
fun watch(
path: Path,
recursive: Boolean = true,
name: String? = null,
): FsWatchRegistration
// Close the whole watcher and all active registrations.
override fun close()
}
object FsWatchers {
fun create(
config: FsWatcherConfig = FsWatcherConfig(),
): FsWatcher
fun isSupported(config: FsWatcherConfig = FsWatcherConfig()): Boolean
}For event semantics, I would keep the first version conservative:
For the implementation, I would follow the native integration pattern already proven by I do not think Internally, Then the Kotlin bridge maps that into the public For the first basic example/demo milestone, I would keep it small but useful for cross-platform validation:
The first manual validation pass could cover:
Implementation-wise, I would split this into small PRs:
Before starting implementation, the main things I would like to confirm are:
|
Uh oh!
There was an error while loading. Please reload this page.
Hi maintainers,
I’d like to contribute the roadmap
fs-watcheritem, but before proposing implementation details, I want to align with your domain-level expectations.Could you guide me on the following?
I’m asking because my own software also involves this domain, and I’d like to contribute in a way that fits your architecture and maintenance style from day one.
Thanks for your guidance.
All reactions