Skip to content

Commit 39572a5

Browse files
author
LoneWandererProductions
committed
Cleanup File Observer
Add it as command and prepare Weaver for new features.
1 parent d264586 commit 39572a5

4 files changed

Lines changed: 223 additions & 55 deletions

File tree

CoreBuilder/FileObserverCommand.cs

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
/*
2+
* COPYRIGHT: See COPYING in the top level directory
3+
* PROJECT: CoreBuilder
4+
* FILE: FileObserverCommand.cs
5+
* PURPOSE: Watches a folder and emits file change events as command outputs. Console based for now.
6+
* PROGRAMMER: Peter Geinitz (Wayfarer)
7+
*/
8+
9+
// ReSharper disable UnusedType.Global
10+
11+
using System;
12+
using System.IO;
13+
using Weaver;
14+
using Weaver.Interfaces;
15+
using Weaver.Messages;
16+
17+
namespace CoreBuilder
18+
{
19+
/// <inheritdoc />
20+
/// <summary>
21+
/// Watches a folder and emits file change events as command outputs.
22+
/// </summary>
23+
public sealed class FileObserverCommand : ICommand
24+
{
25+
/// <inheritdoc />
26+
public string Name => "FileObserver";
27+
28+
/// <inheritdoc />
29+
public string Description => "Monitors a folder and reports file changes.";
30+
31+
/// <inheritdoc />
32+
public string Namespace => "FileManager";
33+
34+
/// <inheritdoc />
35+
/// <summary>
36+
/// Gets the parameter count, 1 for folder path.
37+
/// </summary>
38+
/// <value>
39+
/// The parameter count.
40+
/// </value>
41+
public int ParameterCount => 1;
42+
43+
/// <inheritdoc />
44+
public CommandSignature Signature => new(Namespace, Name, ParameterCount);
45+
46+
/// <summary>
47+
/// The watcher
48+
/// </summary>
49+
private FileSystemWatcher? _watcher;
50+
51+
/// <summary>
52+
/// The mediator, yet to be fully utilized.
53+
/// </summary>
54+
private readonly MessageMediator _mediator;
55+
56+
/// <summary>
57+
/// Initializes a new instance of the <see cref="FileObserverCommand"/> class.
58+
/// </summary>
59+
/// <param name="mediator">The mediator.</param>
60+
public FileObserverCommand(MessageMediator mediator)
61+
{
62+
_mediator = mediator;
63+
}
64+
65+
/// <inheritdoc />
66+
public CommandResult Execute(params string[] args)
67+
{
68+
var path = args[0];
69+
if (!Directory.Exists(path))
70+
return CommandResult.Fail($"Directory does not exist: {path}");
71+
72+
_watcher = new FileSystemWatcher(path)
73+
{
74+
NotifyFilter = NotifyFilters.FileName | NotifyFilters.DirectoryName | NotifyFilters.LastWrite,
75+
IncludeSubdirectories = true,
76+
EnableRaisingEvents = true
77+
};
78+
79+
_watcher.Created += (s, e) => OnEvent("Created", e.FullPath);
80+
_watcher.Changed += (s, e) => OnEvent("Changed", e.FullPath);
81+
_watcher.Deleted += (s, e) => OnEvent("Deleted", e.FullPath);
82+
_watcher.Renamed += (s, e) => OnEvent("Renamed", e.FullPath);
83+
_watcher.Error += (s, e) => OnEvent("Error", e.GetException()?.Message ?? "Unknown error");
84+
85+
return new CommandResult
86+
{
87+
Message = $"Watching folder '{path}' for changes...",
88+
RequiresConfirmation = true,
89+
Feedback = new FeedbackRequest(
90+
prompt: "Send 'stop' to end watching.",
91+
options: new[] { "stop" },
92+
onRespond: input =>
93+
{
94+
if (input.Trim().Equals("stop", StringComparison.OrdinalIgnoreCase))
95+
{
96+
StopWatching();
97+
return CommandResult.Ok("Watcher stopped.");
98+
}
99+
return new CommandResult
100+
{
101+
Message = $"Unknown input '{input}'. Type 'stop' to stop watching.",
102+
RequiresConfirmation = true
103+
};
104+
})
105+
};
106+
}
107+
108+
/// <summary>
109+
/// Called when [event].
110+
/// </summary>
111+
/// <param name="type">The type.</param>
112+
/// <param name="path">The path.</param>
113+
private void OnEvent(string type, string path)
114+
{
115+
// Create a CommandResult for the event
116+
var result = new CommandResult
117+
{
118+
Message = $"[{type}] {path}",
119+
RequiresConfirmation = false
120+
};
121+
122+
// For now we just push to console; mediator registration is only for interactive feedback
123+
Console.WriteLine(result.Message);
124+
125+
// If in future you want to push messages through a mediator, you could add a dedicated pipeline method
126+
//TODO e.g. _mediator.PushMessage(result);
127+
}
128+
129+
/// <summary>
130+
/// Stops the watching.
131+
/// </summary>
132+
private void StopWatching()
133+
{
134+
if (_watcher != null)
135+
{
136+
_watcher.EnableRaisingEvents = false;
137+
_watcher.Dispose();
138+
_watcher = null;
139+
}
140+
}
141+
142+
/// <inheritdoc />
143+
public CommandResult InvokeExtension(string extensionName, params string[] args)
144+
{
145+
return CommandResult.Fail($"'{Name}' has no extensions.");
146+
}
147+
}
148+
}

FileHandler/FileObserver.cs

Lines changed: 67 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -7,79 +7,100 @@
77
*/
88

99
// ReSharper disable UnusedMember.Global
10-
1110
using System;
1211
using System.IO;
12+
using System.Threading;
1313
using System.Threading.Tasks;
1414

1515
namespace FileHandler;
1616

17-
public class FileObserver
17+
/// <summary>
18+
/// Observes a folder for changes and raises async events.
19+
/// Supports cancellation via <see cref="CancellationToken"/>.
20+
/// </summary>
21+
public class FileObserver : IDisposable
1822
{
23+
private readonly FileSystemWatcher _watcher;
24+
1925
/// <summary>
20-
/// Searches the folder asynchronous.
26+
/// Triggered when a file or folder is created.
2127
/// </summary>
22-
/// <param name="path">The path.</param>
23-
public async Task SearchFolderAsync(string path)
24-
{
25-
var watcher = new FileSystemWatcher(path)
26-
{
27-
NotifyFilter = NotifyFilters.DirectoryName | NotifyFilters.FileName,
28-
IncludeSubdirectories = true,
29-
InternalBufferSize = 64 * 1024 // Increase buffer size to handle more events
30-
};
28+
public event Func<FileSystemEventArgs, Task>? Created;
3129

32-
watcher.Created += OnCreated;
33-
watcher.Changed += OnChanged;
34-
watcher.Deleted += OnDeleted;
35-
watcher.Error += OnError;
30+
/// <summary>
31+
/// Triggered when a file or folder is changed.
32+
/// </summary>
33+
public event Func<FileSystemEventArgs, Task>? Changed;
3634

37-
watcher.EnableRaisingEvents = true;
35+
/// <summary>
36+
/// Triggered when a file or folder is deleted.
37+
/// </summary>
38+
public event Func<FileSystemEventArgs, Task>? Deleted;
3839

39-
Console.WriteLine("Monitoring folder for changes. Press any key to stop.");
40-
await Task.Run(Console.ReadKey); // Run in background to avoid blocking
40+
/// <summary>
41+
/// Triggered when a file or folder is renamed.
42+
/// </summary>
43+
public event Func<RenamedEventArgs, Task>? Renamed;
4144

42-
watcher.EnableRaisingEvents = false;
43-
watcher.Dispose();
44-
}
45+
/// <summary>
46+
/// Triggered on watcher errors.
47+
/// </summary>
48+
public event Func<ErrorEventArgs, Task>? Error;
4549

4650
/// <summary>
47-
/// Called when [created].
51+
/// Initializes a new instance of <see cref="FileObserver"/>.
4852
/// </summary>
49-
/// <param name="sender">The sender.</param>
50-
/// <param name="e">The <see cref="FileSystemEventArgs" /> instance containing the event data.</param>
51-
private static void OnCreated(object sender, FileSystemEventArgs e)
53+
/// <param name="path">Folder to observe.</param>
54+
public FileObserver(string path)
5255
{
53-
Console.WriteLine($"File or folder '{e.FullPath}' was created.");
56+
if (!Directory.Exists(path))
57+
throw new DirectoryNotFoundException(path);
58+
59+
_watcher = new FileSystemWatcher(path)
60+
{
61+
NotifyFilter = NotifyFilters.FileName | NotifyFilters.DirectoryName | NotifyFilters.LastWrite,
62+
IncludeSubdirectories = true,
63+
InternalBufferSize = 64 * 1024
64+
};
65+
66+
_watcher.Created += async (s, e) => { if (Created != null) await Created.Invoke(e); };
67+
_watcher.Changed += async (s, e) => { if (Changed != null) await Changed.Invoke(e); };
68+
_watcher.Deleted += async (s, e) => { if (Deleted != null) await Deleted.Invoke(e); };
69+
_watcher.Renamed += async (s, e) => { if (Renamed != null) await Renamed.Invoke(e); };
70+
_watcher.Error += async (s, e) => { if (Error != null) await Error.Invoke(e); };
5471
}
5572

5673
/// <summary>
57-
/// Called when [changed].
74+
/// Starts watching the folder.
5875
/// </summary>
59-
/// <param name="sender">The sender.</param>
60-
/// <param name="e">The <see cref="FileSystemEventArgs" /> instance containing the event data.</param>
61-
private static void OnChanged(object sender, FileSystemEventArgs e)
62-
{
63-
Console.WriteLine($"File or folder '{e.FullPath}' was modified.");
64-
}
76+
public void Start() => _watcher.EnableRaisingEvents = true;
6577

6678
/// <summary>
67-
/// Called when [deleted].
79+
/// Stops watching the folder.
6880
/// </summary>
69-
/// <param name="sender">The sender.</param>
70-
/// <param name="e">The <see cref="FileSystemEventArgs" /> instance containing the event data.</param>
71-
private static void OnDeleted(object sender, FileSystemEventArgs e)
72-
{
73-
Console.WriteLine($"File or folder '{e.FullPath}' was deleted.");
74-
}
81+
public void Stop() => _watcher.EnableRaisingEvents = false;
7582

7683
/// <summary>
77-
/// Called when [error].
84+
/// Runs the observer until cancelled via token.
7885
/// </summary>
79-
/// <param name="sender">The sender.</param>
80-
/// <param name="e">The <see cref="ErrorEventArgs" /> instance containing the event data.</param>
81-
private static void OnError(object sender, ErrorEventArgs e)
86+
/// <param name="cancellationToken">Token to stop watching.</param>
87+
/// <returns>Task that completes when cancelled.</returns>
88+
public Task RunUntilCancelledAsync(CancellationToken cancellationToken)
8289
{
83-
Console.WriteLine("Error occurred: " + e.GetException().Message);
90+
Start();
91+
var tcs = new TaskCompletionSource<object?>();
92+
cancellationToken.Register(() =>
93+
{
94+
Stop();
95+
tcs.TrySetResult(null);
96+
});
97+
return tcs.Task;
8498
}
99+
100+
/// <summary>
101+
/// Disposes the internal watcher.
102+
/// </summary>
103+
public void Dispose() => _watcher.Dispose();
85104
}
105+
106+

SQLiteHelper/SqliteProcessing.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
using System.Data;
1515
using System.Diagnostics;
1616
using System.Globalization;
17-
using System.Linq;
1817

1918
namespace SqliteHelper;
2019

Weaver/Weave.cs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/*
1+
/*
22
* COPYRIGHT: See COPYING in the top level directory
33
* PROJECT: Weaver
44
* FILE: Weave.cs
@@ -51,7 +51,7 @@ private static readonly Dictionary<string, CommandExtension> GlobalExtensions
5151
/// <summary>
5252
/// The mediator
5353
/// </summary>
54-
private readonly MessageMediator _mediator = new();
54+
public readonly MessageMediator Mediator = new();
5555

5656
/// <summary>
5757
/// The pending feedback
@@ -210,7 +210,7 @@ public CommandResult ProcessInput(string raw)
210210
if (_pendingFeedback?.IsPending == true)
211211
{
212212
// Verify the command using mediator
213-
var associatedCommand = _mediator.Resolve(_pendingFeedback.RequestId);
213+
var associatedCommand = Mediator.Resolve(_pendingFeedback.RequestId);
214214
if (associatedCommand == null)
215215
{
216216
// Unexpected state
@@ -222,7 +222,7 @@ public CommandResult ProcessInput(string raw)
222222

223223
if (result.RequiresConfirmation) return result;
224224

225-
_mediator.Clear(_pendingFeedback.RequestId);
225+
Mediator.Clear(_pendingFeedback.RequestId);
226226
_pendingFeedback = null;
227227

228228
return result;
@@ -268,7 +268,7 @@ public CommandResult ProcessInput(string raw)
268268
_pendingFeedback = execResult.Feedback;
269269

270270
// Register feedback with mediator
271-
_mediator.Register(cmd, _pendingFeedback);
271+
Mediator.Register(cmd, _pendingFeedback);
272272
}
273273

274274
return execResult;
@@ -418,7 +418,7 @@ private CommandResult HandleFeedback(CommandResult result, ICommand cmd)
418418
if (result.RequiresConfirmation && result.Feedback != null)
419419
{
420420
_pendingFeedback = result.Feedback;
421-
_mediator.Register(cmd, _pendingFeedback);
421+
Mediator.Register(cmd, _pendingFeedback);
422422
}
423423

424424
return result;
@@ -430,7 +430,7 @@ private CommandResult HandleFeedback(CommandResult result, ICommand cmd)
430430
public void Reset()
431431
{
432432
_pendingFeedback = null;
433-
_mediator.ClearAll();
433+
Mediator.ClearAll();
434434
}
435435
}
436-
}
436+
}

0 commit comments

Comments
 (0)