Skip to content

Commit

Permalink
Merge pull request #1 from Aaronontheweb/full-sample
Browse files Browse the repository at this point in the history
adds a full working sample of a custom command
  • Loading branch information
Aaronontheweb committed Apr 20, 2017
2 parents 2fdf013 + 62c80e1 commit d948144
Show file tree
Hide file tree
Showing 11 changed files with 572 additions and 0 deletions.
63 changes: 63 additions & 0 deletions .gitattributes
@@ -0,0 +1,63 @@
###############################################################################
# Set default behavior to automatically normalize line endings.
###############################################################################
* text=auto

###############################################################################
# Set default behavior for command prompt diff.
#
# This is need for earlier builds of msysgit that does not have it on by
# default for csharp files.
# Note: This is only used by command line
###############################################################################
#*.cs diff=csharp

###############################################################################
# Set the merge driver for project and solution files
#
# Merging from the command prompt will add diff markers to the files if there
# are conflicts (Merging from VS is not affected by the settings below, in VS
# the diff markers are never inserted). Diff markers may cause the following
# file extensions to fail to load in VS. An alternative would be to treat
# these files as binary and thus will always conflict and require user
# intervention with every merge. To do so, just uncomment the entries below
###############################################################################
#*.sln merge=binary
#*.csproj merge=binary
#*.vbproj merge=binary
#*.vcxproj merge=binary
#*.vcproj merge=binary
#*.dbproj merge=binary
#*.fsproj merge=binary
#*.lsproj merge=binary
#*.wixproj merge=binary
#*.modelproj merge=binary
#*.sqlproj merge=binary
#*.wwaproj merge=binary

###############################################################################
# behavior for image files
#
# image files are treated as binary by default.
###############################################################################
#*.jpg binary
#*.png binary
#*.gif binary

###############################################################################
# diff behavior for common document formats
#
# Convert binary document formats to text before diffing them. This feature
# is only available from the command line. Turn it on by uncommenting the
# entries below.
###############################################################################
#*.doc diff=astextplain
#*.DOC diff=astextplain
#*.docx diff=astextplain
#*.DOCX diff=astextplain
#*.dot diff=astextplain
#*.DOT diff=astextplain
#*.pdf diff=astextplain
#*.PDF diff=astextplain
#*.rtf diff=astextplain
#*.RTF diff=astextplain
22 changes: 22 additions & 0 deletions Petabridge.Cmd.QuickStart/Petabridge.Cmd.QuickStart.sln
@@ -0,0 +1,22 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.26403.7
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Petabridge.Cmd.QuickStart", "Petabridge.Cmd.QuickStart\Petabridge.Cmd.QuickStart.csproj", "{31FD95DB-EFC4-435A-87F1-40D2AAAB1AE5}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{31FD95DB-EFC4-435A-87F1-40D2AAAB1AE5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{31FD95DB-EFC4-435A-87F1-40D2AAAB1AE5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{31FD95DB-EFC4-435A-87F1-40D2AAAB1AE5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{31FD95DB-EFC4-435A-87F1-40D2AAAB1AE5}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal
20 changes: 20 additions & 0 deletions Petabridge.Cmd.QuickStart/Petabridge.Cmd.QuickStart/App.config
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>

<configuration>
<configSections>
<section name="akka" type="Akka.Configuration.Hocon.AkkaConfigurationSection, Akka" />
</configSections>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1" />
</startup>
<akka>
<hocon>
<![CDATA[
petabridge.cmd{
# disable logging palettes on startup
log-palettes-on-startup = off
}
]]>
</hocon>
</akka>
</configuration>
@@ -0,0 +1,99 @@
// -----------------------------------------------------------------------
// <copyright file="MessageMemorizerActor.cs" company="Petabridge, LLC">
// Copyright (C) 2017 - 2017 Petabridge, LLC <https://petabridge.com>
// </copyright>
// -----------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Linq;
using Akka.Actor;

namespace Petabridge.Cmd.QuickStart
{
/// <summary>
/// Actor responsible for memorizing messages that will be saved on the server.
/// </summary>
public class MessageMemorizerActor : ReceiveActor
{
private readonly SortedSet<Message> _messages = new SortedSet<Message>();

public MessageMemorizerActor()
{
Receive<Message>(m =>
{
_messages.Add(m);
Sender.Tell(CommandResponse.Empty);
});

Receive<FetchMessages>(f => f.Since == null, f => // all messages
{
foreach (var msg in _messages)
Sender.Tell(new CommandResponse(msg.ToString(), false));
// by setting final:false we signal to client that more responses are coming
Sender.Tell(CommandResponse.Empty); // tells the client not to expect any more responses (final == true)
});

Receive<FetchMessages>(f =>
{
var acceptableTime = DateTime.UtcNow - f.Since;
var matchingMessages =
_messages.Where(x => x.TimeStamp >= acceptableTime).OrderBy(x => x.TimeStamp).ToList();
foreach (var msg in matchingMessages)
Sender.Tell(new CommandResponse(msg.ToString(), false));
// by setting final:false we signal to client that more responses are coming
Sender.Tell(CommandResponse.Empty); // tells the client not to expect any more responses (final == true)
});

Receive<PurgeMessages>(_ =>
{
_messages.Clear();
Sender.Tell(CommandResponse.Empty);
});
}

public class Message : IComparable<Message>
{
public Message(string msg, DateTime timeStamp, string ip)
{
Msg = msg;
TimeStamp = timeStamp;
Ip = ip;
}

public DateTime TimeStamp { get; }
public string Msg { get; }
public string Ip { get; }

public int CompareTo(Message other)
{
if (ReferenceEquals(this, other)) return 0;
if (ReferenceEquals(null, other)) return 1;
return TimeStamp.CompareTo(other.TimeStamp);
}

public override string ToString()
{
return $"[{Ip}][{TimeStamp.ToShortTimeString()}]: {Msg}";
}
}

public class FetchMessages
{
public FetchMessages(TimeSpan? since = null)
{
Since = since;
}

public TimeSpan? Since { get; }
}

public class PurgeMessages
{
public static readonly PurgeMessages Instance = new PurgeMessages();

private PurgeMessages()
{
}
}
}
}
@@ -0,0 +1,121 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Akka.Actor;
using Akka.Configuration;
using Petabridge.Cmd.Host;

namespace Petabridge.Cmd.QuickStart
{
/// <summary>
/// Actor responsible for handling all <see cref="Command"/>s defined inside <see cref="MsgCommands.Palette"/>
/// </summary>
public class MsgCommandHandlerActor : CommandHandlerActor
{
private readonly IActorRef _messageMemorizer;
private static readonly Regex TimeRegex = new Regex(@"^(?<value>([0-9]+(\.[0-9]+)?))\s*(?<unit>(ms|s|h|m|d))$", RegexOptions.Compiled);

public MsgCommandHandlerActor(IActorRef messageMemorizer) : base(MsgCommands.Palette)
{
_messageMemorizer = messageMemorizer;
Receive<Command>(c => c.Name.Equals(MsgCommands.CheckMessages.Name), command => ExecuteCommand(command, HandleFetch));
Receive<Command>(c => c.Name.Equals(MsgCommands.Write.Name), command => ExecuteCommand(command, HandleWrite));
Receive<Command>(c => c.Name.Equals(MsgCommands.Echo.Name), command => ExecuteCommand(command, HandleEcho));
Receive<Command>(c => c.Name.Equals(MsgCommands.Purge.Name), command => ExecuteCommand(command, HandlePurge));
}

public void HandlePurge(Command purge)
{
_messageMemorizer.Tell(MessageMemorizerActor.PurgeMessages.Instance, Sender);
}

public void HandleWrite(Command write)
{
var msg =
write.Arguments.SingleOrDefault(
x => MsgCommands.Write.ArgumentsByName["message"].Switch.Contains(x.Item1))?
.Item2;

_messageMemorizer.Tell(new MessageMemorizerActor.Message(msg, DateTime.UtcNow, Sender.Path.Name), Sender);
}

public void HandleEcho(Command echo)
{
var msg =
echo.Arguments.SingleOrDefault(
x => MsgCommands.Echo.ArgumentsByName["message"].Switch.Contains(x.Item1))?
.Item2;

Sender.Tell(new CommandResponse(new MessageMemorizerActor.Message(msg, DateTime.UtcNow, Sender.Path.Name).ToString())); // will echo what was written on commandline
}

public void HandleFetch(Command fetch)
{
// check if we have a timeframe for the message specified
// what this code does: scans the set of arguments in `fetch` to see
// if any of the switches for the `since` argument have been used, and if
// so return the value for that argument. Otherwise, return null.
var containsTimeframe =
fetch.Arguments.SingleOrDefault(
x => MsgCommands.CheckMessages.ArgumentsByName["since"].Switch.Contains(x.Item1))?.Item2;

if (containsTimeframe == null)
{
_messageMemorizer.Tell(new MessageMemorizerActor.FetchMessages(), Sender); // preserve sender so client gets replies directly
return;
}

// using regular expression to extract the time format
var m = TimeRegex.Match(containsTimeframe);
if (!m.Success)
{
Sender.Tell(new ErroredCommandResponse($"Unable to extract time format from {containsTimeframe}. Should be in format of [value][ms|s|m|h|d] for milliseconds, seconds, minutes, hours, or days respectively."));
return;
}


try
{
var unit = m.Groups["unit"].Value;
var value = Int32.Parse(m.Groups["value"].Value);

if (value < 0)
{
Sender.Tell(new ErroredCommandResponse($"Need to pass in positive time value for `since` argument. Instead, received [{value} {unit}]"));
return;
}

TimeSpan time = TimeSpan.Zero;
switch (unit)
{
case "ms":
time = TimeSpan.FromMilliseconds(value);
break;
case "s":
time = TimeSpan.FromSeconds(value);
break;
case "m":
time = TimeSpan.FromMinutes(value);
break;
case "h":
time = TimeSpan.FromHours(value);
break;
case "d":
time = TimeSpan.FromDays(value);
break;
}

// make sure the original sender is passed along so reply goes to client
_messageMemorizer.Tell(new MessageMemorizerActor.FetchMessages(time), Sender);

}
catch (Exception ex)
{
Sender.Tell(new ErroredCommandResponse($"Error occurred while processing command: {ex.Message}"));
}
}
}
}
@@ -0,0 +1,40 @@
// -----------------------------------------------------------------------
// <copyright file="MsgCommandPaletteHandler.cs" company="Petabridge, LLC">
// Copyright (C) 2017 - 2017 Petabridge, LLC <https://petabridge.com>
// </copyright>
// -----------------------------------------------------------------------
using Akka.Actor;
using Petabridge.Cmd.Host;

namespace Petabridge.Cmd.QuickStart
{
/// <summary>
/// Gets registered with <see cref="PetabridgeCmd" /> in order to enable the server to begin processing
/// <see cref="MsgCommands.Palette" /> using the <see cref="MsgCommandHandlerActor" /> and the
/// <see cref="MessageMemorizerActor" />.
/// </summary>
public class MsgCommandPaletteHandler : CommandPaletteHandler
{
private Props _underlyingProps;

public MsgCommandPaletteHandler()
: base(MsgCommands.Palette) // registers the command palette with this handler.
{
}

public override Props HandlerProps => _underlyingProps;

/*
* Overriding this method gives us the ability to do things like create the MessageMemorizerActor before HandlerProps gets used
*/

public override void OnRegister(PetabridgeCmd plugin)
{
var memorizer = plugin.Sys.ActorOf(Props.Create(() => new MessageMemorizerActor()), "pbm-msg-memorizier");

// will be used to create a new MsgCommandHandlerActor instance per connection
_underlyingProps = Props.Create(() => new MsgCommandHandlerActor(memorizer));
base.OnRegister(plugin);
}
}
}

0 comments on commit d948144

Please sign in to comment.