From 5eefd1c6ba5c72da820a8a09310d8e50e7335a95 Mon Sep 17 00:00:00 2001 From: nathvi Date: Fri, 6 Apr 2018 16:52:46 -0500 Subject: [PATCH 1/2] Moving the FileObserver class definition Moving the FileObserver class definition so that way code that depends on it is created later (no red squiggles to deal with when reading/implementing the document is nice) --- src/Unit-1/lesson4/README.md | 191 ++++++++++++++++++----------------- 1 file changed, 96 insertions(+), 95 deletions(-) diff --git a/src/Unit-1/lesson4/README.md b/src/Unit-1/lesson4/README.md index 71fa64cbc..aef9e8afa 100644 --- a/src/Unit-1/lesson4/README.md +++ b/src/Unit-1/lesson4/README.md @@ -241,6 +241,102 @@ The goal of this exercise is to show you how to make a parent/child actor relati ### Phase 1: Make your first parent/child actors! We're ready to create our actor classes that will form a parent/child relationship. +#### Add `FileObserver` +This is a utility class that we're providing for you to use. It does the low-level work of actually watching a file for changes. + +Create a new class called `FileObserver` and type in the code for [FileObserver.cs](Completed/FileObserver.cs). If you're running this on Mono, note the extra environment variable that has to be uncommented in the `Start()` method: + +```csharp +// FileObserver.cs +using System; +using System.IO; +using Akka.Actor; + +namespace WinTail +{ + /// + /// Turns events about a specific file into + /// messages for . + /// + public class FileObserver : IDisposable + { + private readonly IActorRef _tailActor; + private readonly string _absoluteFilePath; + private FileSystemWatcher _watcher; + private readonly string _fileDir; + private readonly string _fileNameOnly; + + public FileObserver(IActorRef tailActor, string absoluteFilePath) + { + _tailActor = tailActor; + _absoluteFilePath = absoluteFilePath; + _fileDir = Path.GetDirectoryName(absoluteFilePath); + _fileNameOnly = Path.GetFileName(absoluteFilePath); + } + + /// + /// Begin monitoring file. + /// + public void Start() + { + // Need this for Mono 3.12.0 workaround + // uncomment next line if you're running on Mono! + // Environment.SetEnvironmentVariable("MONO_MANAGED_WATCHER", "enabled"); + + // make watcher to observe our specific file + _watcher = new FileSystemWatcher(_fileDir, _fileNameOnly); + + // watch our file for changes to the file name, + // or new messages being written to file + _watcher.NotifyFilter = NotifyFilters.FileName | NotifyFilters.LastWrite; + + // assign callbacks for event types + _watcher.Changed += OnFileChanged; + _watcher.Error += OnFileError; + + // start watching + _watcher.EnableRaisingEvents = true; + } + + /// + /// Stop monitoring file. + /// + public void Dispose() + { + _watcher.Dispose(); + } + + /// + /// Callback for file error events. + /// + /// + /// + void OnFileError(object sender, ErrorEventArgs e) + { + _tailActor.Tell(new TailActor.FileError(_fileNameOnly, + e.GetException().Message), + ActorRefs.NoSender); + } + + /// + /// Callback for file change events. + /// + /// + /// + void OnFileChanged(object sender, FileSystemEventArgs e) + { + if (e.ChangeType == WatcherChangeTypes.Changed) + { + // here we use a special ActorRefs.NoSender + // since this event can happen many times, + // this is a little microoptimization + _tailActor.Tell(new TailActor.FileWrite(e.Name), ActorRefs.NoSender); + } + } + } +} +``` + Recall that in the hierarchy we're going for, there is a `TailCoordinatorActor` that coordinates child actors to actually monitor and tail files. For now it will only supervise one child, `TailActor`, but in the future it can easily expand to have many children, each observing/tailing a different file. #### Add `TailCoordinatorActor` @@ -563,101 +659,6 @@ private void DoPrintInstructions() } ``` -#### Add `FileObserver` -This is a utility class that we're providing for you to use. It does the low-level work of actually watching a file for changes. - -Create a new class called `FileObserver` and type in the code for [FileObserver.cs](Completed/FileObserver.cs). If you're running this on Mono, note the extra environment variable that has to be uncommented in the `Start()` method: - -```csharp -// FileObserver.cs -using System; -using System.IO; -using Akka.Actor; - -namespace WinTail -{ - /// - /// Turns events about a specific file into - /// messages for . - /// - public class FileObserver : IDisposable - { - private readonly IActorRef _tailActor; - private readonly string _absoluteFilePath; - private FileSystemWatcher _watcher; - private readonly string _fileDir; - private readonly string _fileNameOnly; - - public FileObserver(IActorRef tailActor, string absoluteFilePath) - { - _tailActor = tailActor; - _absoluteFilePath = absoluteFilePath; - _fileDir = Path.GetDirectoryName(absoluteFilePath); - _fileNameOnly = Path.GetFileName(absoluteFilePath); - } - - /// - /// Begin monitoring file. - /// - public void Start() - { - // Need this for Mono 3.12.0 workaround - // uncomment next line if you're running on Mono! - // Environment.SetEnvironmentVariable("MONO_MANAGED_WATCHER", "enabled"); - - // make watcher to observe our specific file - _watcher = new FileSystemWatcher(_fileDir, _fileNameOnly); - - // watch our file for changes to the file name, - // or new messages being written to file - _watcher.NotifyFilter = NotifyFilters.FileName | NotifyFilters.LastWrite; - - // assign callbacks for event types - _watcher.Changed += OnFileChanged; - _watcher.Error += OnFileError; - - // start watching - _watcher.EnableRaisingEvents = true; - } - - /// - /// Stop monitoring file. - /// - public void Dispose() - { - _watcher.Dispose(); - } - - /// - /// Callback for file error events. - /// - /// - /// - void OnFileError(object sender, ErrorEventArgs e) - { - _tailActor.Tell(new TailActor.FileError(_fileNameOnly, - e.GetException().Message), - ActorRefs.NoSender); - } - - /// - /// Callback for file change events. - /// - /// - /// - void OnFileChanged(object sender, FileSystemEventArgs e) - { - if (e.ChangeType == WatcherChangeTypes.Changed) - { - // here we use a special ActorRefs.NoSender - // since this event can happen many times, - // this is a little microoptimization - _tailActor.Tell(new TailActor.FileWrite(e.Name), ActorRefs.NoSender); - } - } - } -} -``` From e50d16a15270baf6b4306ea2b496243555b7648d Mon Sep 17 00:00:00 2001 From: nathvi Date: Fri, 6 Apr 2018 17:06:14 -0500 Subject: [PATCH 2/2] Moving TailActor to be first definition Moving TailActor to be first definition so that way classes are defined in increasing dependencies as the document progresses. --- src/Unit-1/lesson4/README.md | 250 +++++++++++++++++------------------ 1 file changed, 123 insertions(+), 127 deletions(-) diff --git a/src/Unit-1/lesson4/README.md b/src/Unit-1/lesson4/README.md index aef9e8afa..717b0b1aa 100644 --- a/src/Unit-1/lesson4/README.md +++ b/src/Unit-1/lesson4/README.md @@ -241,9 +241,131 @@ The goal of this exercise is to show you how to make a parent/child actor relati ### Phase 1: Make your first parent/child actors! We're ready to create our actor classes that will form a parent/child relationship. +#### Add `TailActor` +Add a class called `TailActor` in its own file. This actor is the actor that is actually responsible for tailing a given file. `TailActor` will be created and supervised by `TailCoordinatorActor` in a moment. +For now, add the following code in `TailActor.cs`: + +```csharp +// TailActor.cs +using System.IO; +using System.Text; +using Akka.Actor; + +namespace WinTail +{ + /// + /// Monitors the file at for changes and sends + /// file updates to console. + /// + public class TailActor : UntypedActor + { + #region Message types + + /// + /// Signal that the file has changed, and we need to + /// read the next line of the file. + /// + public class FileWrite + { + public FileWrite(string fileName) + { + FileName = fileName; + } + + public string FileName { get; private set; } + } + + /// + /// Signal that the OS had an error accessing the file. + /// + public class FileError + { + public FileError(string fileName, string reason) + { + FileName = fileName; + Reason = reason; + } + + public string FileName { get; private set; } + + public string Reason { get; private set; } + } + + /// + /// Signal to read the initial contents of the file at actor startup. + /// + public class InitialRead + { + public InitialRead(string fileName, string text) + { + FileName = fileName; + Text = text; + } + + public string FileName { get; private set; } + public string Text { get; private set; } + } + + #endregion + + private readonly string _filePath; + private readonly IActorRef _reporterActor; + private readonly FileObserver _observer; + private readonly Stream _fileStream; + private readonly StreamReader _fileStreamReader; + + public TailActor(IActorRef reporterActor, string filePath) + { + _reporterActor = reporterActor; + _filePath = filePath; + + // start watching file for changes + _observer = new FileObserver(Self, Path.GetFullPath(_filePath)); + _observer.Start(); + + // open the file stream with shared read/write permissions + // (so file can be written to while open) + _fileStream = new FileStream(Path.GetFullPath(_filePath), + FileMode.Open, FileAccess.Read, FileShare.ReadWrite); + _fileStreamReader = new StreamReader(_fileStream, Encoding.UTF8); + + // read the initial contents of the file and send it to console as first msg + var text = _fileStreamReader.ReadToEnd(); + Self.Tell(new InitialRead(_filePath, text)); + } + + protected override void OnReceive(object message) + { + if (message is FileWrite) + { + // move file cursor forward + // pull results from cursor to end of file and write to output + // (this is assuming a log file type format that is append-only) + var text = _fileStreamReader.ReadToEnd(); + if (!string.IsNullOrEmpty(text)) + { + _reporterActor.Tell(text); + } + + } + else if (message is FileError) + { + var fe = message as FileError; + _reporterActor.Tell(string.Format("Tail error: {0}", fe.Reason)); + } + else if (message is InitialRead) + { + var ir = message as InitialRead; + _reporterActor.Tell(ir.Text); + } + } + } +} +``` + + #### Add `FileObserver` This is a utility class that we're providing for you to use. It does the low-level work of actually watching a file for changes. - Create a new class called `FileObserver` and type in the code for [FileObserver.cs](Completed/FileObserver.cs). If you're running this on Mono, note the extra environment variable that has to be uncommented in the `Start()` method: ```csharp @@ -399,135 +521,9 @@ namespace WinTail ``` - -#### Add `TailActor` -Now, add a class called `TailActor` in its own file. This actor is the actor that is actually responsible for tailing a given file. `TailActor` will be created and supervised by `TailCoordinatorActor` in a moment. - -For now, add the following code in `TailActor.cs`: - -```csharp -// TailActor.cs -using System.IO; -using System.Text; -using Akka.Actor; - -namespace WinTail -{ - /// - /// Monitors the file at for changes and sends - /// file updates to console. - /// - public class TailActor : UntypedActor - { - #region Message types - - /// - /// Signal that the file has changed, and we need to - /// read the next line of the file. - /// - public class FileWrite - { - public FileWrite(string fileName) - { - FileName = fileName; - } - - public string FileName { get; private set; } - } - - /// - /// Signal that the OS had an error accessing the file. - /// - public class FileError - { - public FileError(string fileName, string reason) - { - FileName = fileName; - Reason = reason; - } - - public string FileName { get; private set; } - - public string Reason { get; private set; } - } - - /// - /// Signal to read the initial contents of the file at actor startup. - /// - public class InitialRead - { - public InitialRead(string fileName, string text) - { - FileName = fileName; - Text = text; - } - - public string FileName { get; private set; } - public string Text { get; private set; } - } - - #endregion - - private readonly string _filePath; - private readonly IActorRef _reporterActor; - private readonly FileObserver _observer; - private readonly Stream _fileStream; - private readonly StreamReader _fileStreamReader; - - public TailActor(IActorRef reporterActor, string filePath) - { - _reporterActor = reporterActor; - _filePath = filePath; - - // start watching file for changes - _observer = new FileObserver(Self, Path.GetFullPath(_filePath)); - _observer.Start(); - - // open the file stream with shared read/write permissions - // (so file can be written to while open) - _fileStream = new FileStream(Path.GetFullPath(_filePath), - FileMode.Open, FileAccess.Read, FileShare.ReadWrite); - _fileStreamReader = new StreamReader(_fileStream, Encoding.UTF8); - - // read the initial contents of the file and send it to console as first msg - var text = _fileStreamReader.ReadToEnd(); - Self.Tell(new InitialRead(_filePath, text)); - } - - protected override void OnReceive(object message) - { - if (message is FileWrite) - { - // move file cursor forward - // pull results from cursor to end of file and write to output - // (this is assuming a log file type format that is append-only) - var text = _fileStreamReader.ReadToEnd(); - if (!string.IsNullOrEmpty(text)) - { - _reporterActor.Tell(text); - } - - } - else if (message is FileError) - { - var fe = message as FileError; - _reporterActor.Tell(string.Format("Tail error: {0}", fe.Reason)); - } - else if (message is InitialRead) - { - var ir = message as InitialRead; - _reporterActor.Tell(ir.Text); - } - } - } -} -``` - #### Add `TailActor` as a child of `TailCoordinatorActor` Quick review: `TailActor` is to be a child of `TailCoordinatorActor` and will therefore be supervised by `TailCoordinatorActor`. - This also means that `TailActor` must be created in the context of `TailCoordinatorActor`. - Go to `TailCoordinatorActor.cs` and replace `OnReceive()` with the following code to create your first child actor! ```csharp