Skip to content

Commit acb3181

Browse files
author
Wayfarer
committed
improve and refine my Commands
1 parent 51c9924 commit acb3181

8 files changed

Lines changed: 238 additions & 30 deletions

File tree

CoreBuilder/AnalyzerFactory.cs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
* PROGRAMMER: Peter Geinitz (Wayfarer)
77
*/
88

9+
using CoreBuilder.FileManager;
910
using CoreBuilder.Interface;
1011
using CoreBuilder.Rules;
1112
using System;
@@ -29,6 +30,8 @@ public static IReadOnlyList<ICommand> GetCommands()
2930
ICommand[] modules =
3031
{
3132
new DirectorySizeAnalyzer(),
33+
new DirectorySizeAnalyzer(),
34+
new LogTailCommand(),
3235
new HeaderExtractor(),
3336
new ResXtract(),
3437
new AllocationAnalyzer(),
@@ -91,7 +94,9 @@ public static IReadOnlyList<ICommand> GetCommands(string userspace)
9194
{
9295
new DirectorySizeAnalyzer(),
9396
new FileLockScanner(),
94-
new SmartPingPro()
97+
new SmartPingPro(),
98+
new DirectorySizeAnalyzer(),
99+
new LogTailCommand()
95100
};
96101

97102
// Filter by Namespace
@@ -100,4 +105,4 @@ public static IReadOnlyList<ICommand> GetCommands(string userspace)
100105
.ToList();
101106
}
102107
}
103-
}
108+
}

CoreBuilder/DirectorySizeAnalyzer.cs renamed to CoreBuilder/FileManager/DirectorySizeAnalyzer.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/*
22
* COPYRIGHT: See COPYING in the top level directory
3-
* PROJECT: CoreBuilder
3+
* PROJECT: CoreBuilder.FileManager
44
* FILE: DirectorySizeAnalyzer.cs
55
* PURPOSE: Command to analyze and display file sizes and total percentage usage.
66
* PROGRAMMER: Peter Geinitz (Wayfarer)
@@ -20,7 +20,7 @@
2020
using Weaver.Interfaces;
2121
using Weaver.Messages;
2222

23-
namespace CoreBuilder;
23+
namespace CoreBuilder.FileManager;
2424

2525
/// <inheritdoc />
2626
/// <summary>
@@ -174,4 +174,4 @@ public CommandResult InvokeExtension(string extensionName, params string[] args)
174174
{
175175
return CommandResult.Fail($"'{Name}' has no extensions.");
176176
}
177-
}
177+
}

CoreBuilder/FileLockScanner.cs renamed to CoreBuilder/FileManager/FileLockScanner.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/*
22
* COPYRIGHT: See COPYING in the top level directory
3-
* PROJECT: CoreBuilder
3+
* PROJECT: CoreBuilder.FileManager
44
* FILE: FileLockScanner.cs
55
* PURPOSE: Command to find locked files in a directory.
66
* PROGRAMMER: Peter Geinitz (Wayfarer)
@@ -17,13 +17,13 @@
1717
using Weaver.Interfaces;
1818
using Weaver.Messages;
1919

20-
namespace CoreBuilder;
20+
namespace CoreBuilder.FileManager;
2121

2222
/// <inheritdoc />
2323
/// <summary>
2424
/// Simple command to scan a directory for locked files and list the processes locking them.
2525
/// </summary>
26-
/// <seealso cref="Weaver.Interfaces.ICommand" />
26+
/// <seealso cref="ICommand" />
2727
public sealed class FileLockScanner : ICommand
2828
{
2929
/// <inheritdoc />
@@ -113,4 +113,4 @@ private static string[] GetLockingProcesses(string file)
113113
/// <inheritdoc />
114114
public CommandResult InvokeExtension(string extensionName, params string[] args)
115115
=> CommandResult.Fail($"'{Name}' has no extensions.");
116-
}
116+
}

CoreBuilder/FileObserverCommand.cs renamed to CoreBuilder/FileManager/FileObserverCommand.cs

Lines changed: 10 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/*
22
* COPYRIGHT: See COPYING in the top level directory
3-
* PROJECT: CoreBuilder
3+
* PROJECT: CoreBuilder.FileManager
44
* FILE: FileObserverCommand.cs
55
* PURPOSE: Watches a folder and emits file change events as command outputs. Console based for now.
66
* PROGRAMMER: Peter Geinitz (Wayfarer)
@@ -10,11 +10,13 @@
1010

1111
using System;
1212
using System.IO;
13+
using CoreBuilder.Helper;
14+
using CoreBuilder.Interface;
1315
using Weaver;
1416
using Weaver.Interfaces;
1517
using Weaver.Messages;
1618

17-
namespace CoreBuilder
19+
namespace CoreBuilder.FileManager
1820
{
1921
/// <inheritdoc />
2022
/// <summary>
@@ -49,17 +51,17 @@ public sealed class FileObserverCommand : ICommand
4951
private FileSystemWatcher? _watcher;
5052

5153
/// <summary>
52-
/// The mediator, yet to be fully utilized.
54+
/// The output
5355
/// </summary>
54-
private readonly MessageMediator _mediator;
56+
private readonly IEventOutput _output;
5557

5658
/// <summary>
5759
/// Initializes a new instance of the <see cref="FileObserverCommand"/> class.
5860
/// </summary>
59-
/// <param name="mediator">The mediator.</param>
60-
public FileObserverCommand(MessageMediator mediator)
61+
/// <param name="output">The side channel for output.</param>
62+
public FileObserverCommand(IEventOutput? output = null)
6163
{
62-
_mediator = mediator;
64+
_output = output ?? new ConsoleEventOutput();
6365
}
6466

6567
/// <inheritdoc />
@@ -112,18 +114,7 @@ public CommandResult Execute(params string[] args)
112114
/// <param name="path">The path.</param>
113115
private void OnEvent(string type, string path)
114116
{
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);
117+
_output.Write($"[{type}] {path}");
127118
}
128119

129120
/// <summary>
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
/*
2+
* COPYRIGHT: See COPYING in the top level directory
3+
* PROJECT: CoreBuilder.FileManager
4+
* FILE: LogTailCommand.cs
5+
* PURPOSE: A simple log tail command that watches a file for new lines and prints them to the output.
6+
* PROGRAMMER: Peter Geinitz (Wayfarer)
7+
*/
8+
9+
// ReSharper disable UnusedType.Global
10+
11+
using System;
12+
using System.IO;
13+
using CoreBuilder.Helper;
14+
using CoreBuilder.Interface;
15+
using Weaver;
16+
using Weaver.Interfaces;
17+
using Weaver.Messages;
18+
19+
namespace CoreBuilder.FileManager
20+
{
21+
/// <inheritdoc />
22+
/// <summary>
23+
/// A simple log tail command that watches a file for new lines and prints them to the output.
24+
/// </summary>
25+
/// <seealso cref="ICommand" />
26+
public sealed class LogTailCommand : ICommand
27+
{
28+
/// <inheritdoc />
29+
public string Name => "LogTail";
30+
31+
/// <inheritdoc />
32+
public string Description => "Watches a file and prints newly appended lines.";
33+
34+
/// <inheritdoc />
35+
public string Namespace => "FileManager";
36+
37+
/// <inheritdoc />
38+
public int ParameterCount => 1;
39+
40+
/// <inheritdoc />
41+
public CommandSignature Signature => new(Namespace, Name, ParameterCount);
42+
43+
/// <summary>
44+
/// The output
45+
/// </summary>
46+
private readonly IEventOutput _output;
47+
48+
/// <summary>
49+
/// The watcher
50+
/// </summary>
51+
private FileSystemWatcher? _watcher;
52+
53+
/// <summary>
54+
/// The last length
55+
/// </summary>
56+
private long _lastLength;
57+
58+
/// <summary>
59+
/// Initializes a new instance of the <see cref="LogTailCommand"/> class.
60+
/// </summary>
61+
/// <param name="output">The output.</param>
62+
public LogTailCommand(IEventOutput? output = null)
63+
{
64+
_output = output ?? new ConsoleEventOutput();
65+
}
66+
67+
/// <inheritdoc />
68+
public CommandResult Execute(params string[] args)
69+
{
70+
var filePath = args[0];
71+
72+
if (!File.Exists(filePath))
73+
return CommandResult.Fail($"File does not exist: {filePath}");
74+
75+
var directory = Path.GetDirectoryName(filePath)!;
76+
var fileName = Path.GetFileName(filePath);
77+
78+
_lastLength = new FileInfo(filePath).Length;
79+
80+
_watcher = new FileSystemWatcher(directory)
81+
{
82+
Filter = fileName,
83+
NotifyFilter = NotifyFilters.Size | NotifyFilters.LastWrite,
84+
EnableRaisingEvents = true
85+
};
86+
87+
_watcher.Changed += (s, e) => ReadNewContent(filePath);
88+
89+
return new CommandResult
90+
{
91+
Message = $"Watching file '{filePath}' for new content...",
92+
RequiresConfirmation = true,
93+
Feedback = new FeedbackRequest(
94+
prompt: "Send 'stop' to end watching.",
95+
options: new[] { "stop" },
96+
onRespond: input =>
97+
{
98+
if (input.Trim().Equals("stop", StringComparison.OrdinalIgnoreCase))
99+
{
100+
StopWatching();
101+
return CommandResult.Ok("Log watching stopped.");
102+
}
103+
104+
return new CommandResult
105+
{
106+
Message = $"Unknown input '{input}'. Type 'stop'.",
107+
RequiresConfirmation = true
108+
};
109+
})
110+
};
111+
}
112+
113+
/// <summary>
114+
/// Reads the new content.
115+
/// </summary>
116+
/// <param name="filePath">The file path.</param>
117+
private void ReadNewContent(string filePath)
118+
{
119+
try
120+
{
121+
var fileInfo = new FileInfo(filePath);
122+
if (fileInfo.Length < _lastLength)
123+
{
124+
// file rotated or truncated
125+
_lastLength = 0;
126+
}
127+
128+
using var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
129+
fs.Seek(_lastLength, SeekOrigin.Begin);
130+
131+
using var reader = new StreamReader(fs);
132+
var newText = reader.ReadToEnd();
133+
134+
if (!string.IsNullOrEmpty(newText))
135+
_output.Write(newText.TrimEnd());
136+
137+
_lastLength = fileInfo.Length;
138+
}
139+
catch (Exception ex)
140+
{
141+
_output.Write($"[ERROR] {ex.Message}");
142+
}
143+
}
144+
145+
/// <summary>
146+
/// Stops the watching.
147+
/// </summary>
148+
private void StopWatching()
149+
{
150+
if (_watcher != null)
151+
{
152+
_watcher.EnableRaisingEvents = false;
153+
_watcher.Dispose();
154+
_watcher = null;
155+
}
156+
}
157+
158+
/// <inheritdoc />
159+
public CommandResult InvokeExtension(string extensionName, params string[] args)
160+
=> CommandResult.Fail($"'{Name}' has no extensions.");
161+
}
162+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*
2+
* COPYRIGHT: See COPYING in the top level directory
3+
* PROJECT: CoreBuilder.Helper
4+
* FILE: ConsoleEventOutput.cs
5+
* PURPOSE: Sample console side channel for event output.
6+
* PROGRAMMER: Peter Geinitz (Wayfarer)
7+
*/
8+
9+
using System;
10+
using CoreBuilder.Interface;
11+
12+
namespace CoreBuilder.Helper
13+
{
14+
/// <inheritdoc />
15+
/// <summary>
16+
/// Console side channel for event output.
17+
/// </summary>
18+
/// <seealso cref="CoreBuilder.Interface.IEventOutput" />
19+
public sealed class ConsoleEventOutput : IEventOutput
20+
{
21+
/// <inheritdoc />
22+
public void Write(string message) => Console.WriteLine(message);
23+
}
24+
25+
}

CoreBuilder/Interface/ICodeAnalyzer.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
* COPYRIGHT: See COPYING in the top level directory
33
* PROJECT: CoreBuilder.Interface
44
* FILE: ICodeAnalyzer.cs
5-
* PURPOSE: Your file purpose here
5+
* PURPOSE: Simple interface for code analyzers.
66
* PROGRAMMER: Peter Geinitz (Wayfarer)
77
*/
88

@@ -47,4 +47,4 @@ public interface ICodeAnalyzer
4747
/// Override this only if your analyzer requires project-wide context.
4848
/// </summary>
4949
IEnumerable<Diagnostic> AnalyzeProject(Dictionary<string, string> allFiles) => Enumerable.Empty<Diagnostic>();
50-
}
50+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*
2+
* COPYRIGHT: See COPYING in the top level directory
3+
* PROJECT: CoreBuilder.Interface
4+
* FILE: IEventOutput.cs
5+
* PURPOSE: Simple interface for side channel event output.
6+
* PROGRAMMER: Peter Geinitz (Wayfarer)
7+
*/
8+
9+
// ReSharper disable UnusedMemberInSuper.Global
10+
11+
namespace CoreBuilder.Interface
12+
{
13+
/// <summary>
14+
/// Define an Interface for Event Output, can be implemented by Console, File, etc.
15+
/// </summary>
16+
public interface IEventOutput
17+
{
18+
/// <summary>
19+
/// Writes the specified message.
20+
/// </summary>
21+
/// <param name="message">The message.</param>
22+
void Write(string message);
23+
}
24+
25+
}

0 commit comments

Comments
 (0)