This commit is contained in:
2025-02-05 09:19:16 +03:00
parent 430586cc89
commit 757358fded
5 changed files with 135 additions and 279 deletions

48
examples/osal/notifier.vsh Executable file
View File

@@ -0,0 +1,48 @@
#!/usr/bin/env -S v -n -w -gc none -cc tcc -d use_openssl -enable-globals run
import freeflowuniverse.herolib.osal.notifier
import os
import time
fn on_file_change(event notifier.NotifyEvent, path string) {
match event {
.create { println('File created: ${path}') }
.modify { println('File modified: ${path}') }
.delete { println('File deleted: ${path}') }
.rename { println('File renamed: ${path}') }
}
}
fn main() {
// Create test directory and files
test_dir := '/tmp/notifytest'
if !os.exists(test_dir) {
os.mkdir_all(test_dir)!
os.write_file('${test_dir}/test.txt', 'initial content')!
os.mkdir('${test_dir}/subdir')!
os.write_file('${test_dir}/subdir/test2.txt', 'test content')!
}
// Create a new notifier
mut n := notifier.new('test_watcher')!
// Add files to watch
n.add_watch('${test_dir}', on_file_change)!
// Start watching
n.start()!
println('Watching files in ${test_dir} for 60 seconds...')
println('Try these operations to test the notifier:')
println('1. Modify a file: echo "new content" > ${test_dir}/test.txt')
println('2. Create a file: touch ${test_dir}/newfile.txt')
println('3. Delete a file: rm ${test_dir}/test.txt')
println('4. Rename a file: mv ${test_dir}/test.txt ${test_dir}/renamed.txt')
// Keep the program running for 60 seconds
time.sleep(60 * time.second)
// Clean up
n.stop()
println('\nWatch period ended.')
}

View File

@@ -1,6 +1,5 @@
module notifier module notifier
import os.notify
import os import os
import time import time
@@ -20,24 +19,27 @@ struct WatchEntry {
pub mut: pub mut:
path string path string
callback ?NotifyCallback callback ?NotifyCallback
fd int pid int
} }
// Notifier manages file system notifications // Notifier manages file system notifications using fswatch
pub struct Notifier { pub struct Notifier {
pub mut: pub mut:
name string name string
watcher notify.FdNotifier
watch_list []WatchEntry watch_list []WatchEntry
is_watching bool is_watching bool
} }
// new creates a new Notifier instance // new creates a new Notifier instance
pub fn new(name string) !&Notifier { pub fn new(name string) !&Notifier {
// Check if fswatch is installed
if !os.exists_in_system_path('fswatch') {
return error('fswatch is not installed. Please install it first.')
}
return &Notifier{ return &Notifier{
name: name name: name
watcher: notify.new()! watch_list: []WatchEntry{}
watch_list: []WatchEntry{}
is_watching: false is_watching: false
} }
} }
@@ -48,16 +50,10 @@ pub fn (mut n Notifier) add_watch(path string, callback NotifyCallback) ! {
return error('Path does not exist: ${path}') return error('Path does not exist: ${path}')
} }
mut f := os.open(path)!
fd := f.fd
f.close()
n.watcher.add(fd, .write | .read, .edge_trigger)!
n.watch_list << WatchEntry{ n.watch_list << WatchEntry{
path: path path: path
callback: callback callback: callback
fd: fd pid: 0
} }
println('Added watch for: ${path}') println('Added watch for: ${path}')
@@ -67,7 +63,9 @@ pub fn (mut n Notifier) add_watch(path string, callback NotifyCallback) ! {
pub fn (mut n Notifier) remove_watch(path string) ! { pub fn (mut n Notifier) remove_watch(path string) ! {
for i, entry in n.watch_list { for i, entry in n.watch_list {
if entry.path == path { if entry.path == path {
n.watcher.remove(entry.fd) or { return err } if entry.pid > 0 {
os.system('kill ${entry.pid}')
}
n.watch_list.delete(i) n.watch_list.delete(i)
println('Removed watch for: ${path}') println('Removed watch for: ${path}')
return return
@@ -86,31 +84,62 @@ pub fn (mut n Notifier) start() ! {
} }
n.is_watching = true n.is_watching = true
go n.watch_loop()
// Start a watcher for each path
for mut entry in n.watch_list {
go n.watch_path(mut entry)
}
} }
// stop stops watching for events // stop stops watching for events
pub fn (mut n Notifier) stop() { pub fn (mut n Notifier) stop() {
n.is_watching = false n.is_watching = false
} // Kill all fswatch processes
for entry in n.watch_list {
fn (mut n Notifier) watch_loop() { if entry.pid > 0 {
for n.is_watching { os.system('kill ${entry.pid}')
event := n.watcher.wait(time.Duration(time.hour * 1)) }
println(event)
panic('implement')
// for entry in n.watch_list {
// if event.fd == entry.fd {
// mut notify_event := NotifyEvent.modify
// if event.kind == .create {
// notify_event = .create
// } else if event.kind == .write {
// notify_event = .write
// }
// if entry.callback != none {
// entry.callback(notify_event, entry.path)
// }
// }
// }
} }
} }
fn (mut n Notifier) watch_path(mut entry WatchEntry) {
// Start fswatch process
mut p := os.new_process('/opt/homebrew/bin/fswatch')
p.set_args(['-x', '--event-flags', entry.path])
p.set_redirect_stdio()
p.run()
entry.pid = p.pid
for n.is_watching {
line := p.stdout_read()
if line.len > 0 {
parts := line.split(' ')
if parts.len >= 2 {
path := parts[0]
flags := parts[1]
mut event := NotifyEvent.modify // Default to modify
// Parse fswatch event flags
// See: https://emcrisostomo.github.io/fswatch/doc/1.17.1/fswatch.html#Event-Flags
if flags.contains('Created') {
event = .create
} else if flags.contains('Removed') {
event = .delete
} else if flags.contains('Renamed') {
event = .rename
} else if flags.contains('Updated') || flags.contains('Modified') {
event = .modify
}
if cb := entry.callback {
cb(event, path)
}
}
}
time.sleep(100 * time.millisecond)
}
p.close()
}

View File

@@ -1,136 +0,0 @@
module notifier
import time
import os
const test_file = 'test_watch.txt'
const test_file2 = 'test_watch2.txt'
fn testsuite_begin() {
if os.exists(test_file) {
os.rm(test_file) or {}
}
}
fn testsuite_end() {
if os.exists(test_file) {
os.rm(test_file) or {}
}
}
fn test_notifier() {
mut event_received := false
mut last_event := NotifyEvent.create
on_file_change := fn [mut event_received, mut last_event] (event NotifyEvent, path string) {
event_received = true
last_event = event
}
// Create notifier
mut n := new('test_watcher')!
// Create test file
os.write_file(test_file, 'initial content')!
// Add watch
n.add_watch(test_file, on_file_change)!
// Start watching
n.start()!
// Test file modification
time.sleep(100 * time.millisecond)
os.write_file(test_file, 'modified content')!
time.sleep(500 * time.millisecond)
assert event_received == true
assert last_event == .modify
// Test file deletion
event_received = false
os.rm(test_file)!
time.sleep(500 * time.millisecond)
assert event_received == true
assert last_event == .delete
// Stop watching
n.stop()
}
fn test_multiple_watches() {
mut events_count := 0
on_any_change := fn [mut events_count] (event NotifyEvent, path string) {
events_count++
}
// Create notifier
mut n := new('multi_watcher')!
// Create test files
os.write_file(test_file, 'file1')!
os.write_file(test_file2, 'file2')!
// Add watches
n.add_watch(test_file, on_any_change)!
n.add_watch(test_file2, on_any_change)!
// Start watching
n.start()!
// Modify both files
time.sleep(100 * time.millisecond)
os.write_file(test_file, 'file1 modified')!
os.write_file(test_file2, 'file2 modified')!
time.sleep(500 * time.millisecond)
assert events_count == 2
// Cleanup
n.stop()
os.rm(test_file)!
os.rm(test_file2)!
}
fn test_remove_watch() {
mut events_count := 0
on_change := fn [mut events_count] (event NotifyEvent, path string) {
events_count++
}
// Create notifier
mut n := new('remove_test')!
// Create test file
os.write_file(test_file, 'content')!
// Add watch
n.add_watch(test_file, on_change)!
// Start watching
n.start()!
// Modify file
time.sleep(100 * time.millisecond)
os.write_file(test_file, 'modified')!
time.sleep(500 * time.millisecond)
assert events_count == 1
// Remove watch
n.remove_watch(test_file)!
// Modify file again
os.write_file(test_file, 'modified again')!
time.sleep(500 * time.millisecond)
// Should still be 1 since watch was removed
assert events_count == 1
// Cleanup
n.stop()
os.rm(test_file)!
}

View File

@@ -1,21 +1,24 @@
# Notifier Module # Notifier
The Notifier module provides a simple and efficient way to monitor file system changes in V programs. It wraps the OS-level file system notification mechanisms and provides a clean API for watching files and directories. A file system notification system for V that provides real-time monitoring of file system events using `fswatch`.
## Dependencies
- `fswatch`: Must be installed on your system. The notifier will check for its presence and return an error if not found.
## Features ## Features
- Watch multiple files/paths simultaneously - Monitor file system events (create, modify, delete, rename)
- Event-based callbacks for file changes - Multiple watch paths support
- Support for different types of events (create, modify, delete) - Customizable event callbacks
- Clean API for adding and removing watches - Clean start/stop functionality
## Usage ## Usage Example
### Basic Example
```v ```v
import freeflowuniverse.herolib.osal.notifier import freeflowuniverse.herolib.osal.notifier
// Define callback function for file events
fn on_file_change(event notifier.NotifyEvent, path string) { fn on_file_change(event notifier.NotifyEvent, path string) {
match event { match event {
.create { println('File created: ${path}') } .create { println('File created: ${path}') }
@@ -26,111 +29,18 @@ fn on_file_change(event notifier.NotifyEvent, path string) {
} }
fn main() { fn main() {
// Create a new notifier // Create a new notifier instance
mut n := notifier.new('my_watcher')! mut n := notifier.new('my_watcher')!
// Add a file to watch // Add a path to watch
n.add_watch('path/to/file.txt', on_file_change)! n.add_watch('/path/to/watch', on_file_change)!
// Start watching // Start watching
n.start()! n.start()!
// Keep the program running // ... your application logic ...
for {}
}
```
### Advanced Usage
```v
import freeflowuniverse.herolib.osal.notifier
fn main() {
mut n := notifier.new('config_watcher')!
// Watch multiple files
n.add_watch('config.json', on_config_change)!
n.add_watch('data.txt', on_data_change)!
// Start watching
n.start()!
// ... do other work ...
// Stop watching when done // Stop watching when done
n.stop() n.stop()
} }
fn on_config_change(event notifier.NotifyEvent, path string) {
if event == .modify {
println('Config file changed, reloading...')
// Reload configuration
}
}
fn on_data_change(event notifier.NotifyEvent, path string) {
println('Data file changed: ${event}')
}
``` ```
## API Reference
### Structs
#### Notifier
```v
pub struct Notifier {
pub mut:
name string
is_watching bool
}
```
### Functions
#### new
```v
pub fn new(name string) !&Notifier
```
Creates a new Notifier instance with the given name.
#### add_watch
```v
pub fn (mut n Notifier) add_watch(path string, callback NotifyCallback) !
```
Adds a path to watch with an associated callback function.
#### remove_watch
```v
pub fn (mut n Notifier) remove_watch(path string) !
```
Removes a watched path.
#### start
```v
pub fn (mut n Notifier) start() !
```
Begins watching for file system events.
#### stop
```v
pub fn (mut n Notifier) stop()
```
Stops watching for events.
## Error Handling
The module uses V's error handling system. Most functions return a `!` type, indicating they can fail. Always handle potential errors appropriately:
```v
n := notifier.new('watcher') or {
println('Failed to create notifier: ${err}')
return
}
```
## Notes
- The notifier uses OS-level file system notification mechanisms for efficiency
- Callbacks are executed in a separate thread to avoid blocking
- Always call `stop()` when you're done watching to clean up resources

View File

@@ -32,6 +32,7 @@ pub mut:
// publish_path string // publish_path string
build_path string build_path string
production bool production bool
watch_changes bool
} }
pub fn (mut f DocusaurusFactory) build_dev(args_ DSiteNewArgs) !&DocSite { pub fn (mut f DocusaurusFactory) build_dev(args_ DSiteNewArgs) !&DocSite {
@@ -70,6 +71,10 @@ pub fn (mut f DocusaurusFactory) dev(args_ DSiteNewArgs) !&DocSite {
' '
retry: 0 retry: 0
)! )!
if args.watch_changes{
//TODO: use lib/osal/notifier module to see all changes in docs directory of the source, when changes copy the file to the dest
//TODO: only look at files not starting with # and ending with .md, also for images (.png, .jpeg, .jpg)
}
return s return s
} }