s
This commit is contained in:
48
examples/osal/notifier.vsh
Executable file
48
examples/osal/notifier.vsh
Executable 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.')
|
||||||
|
}
|
||||||
@@ -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()
|
||||||
|
}
|
||||||
|
|||||||
@@ -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)!
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user