Skip to content

Instantly share code, notes, and snippets.

@ReubenBond
Created May 14, 2015 01:37
Show Gist options
  • Save ReubenBond/a16f7ace984eadf98b75 to your computer and use it in GitHub Desktop.
Save ReubenBond/a16f7ace984eadf98b75 to your computer and use it in GitHub Desktop.
ChatRoomGrain snippet
"Don't expect this to compile - it's just a snippet"
/// <summary>
/// The chat room grain.
/// </summary>
[StorageProvider(ProviderName = "chats")]
[Reentrant]
public class ChatRoomGrain : Grain<IState<ChatRoom>>, IChatRoomGrain
{
/// <summary>
/// The address.
/// </summary>
private Address address;
/// <summary>
/// The log.
/// </summary>
private Logger log;
/// <summary>
/// Activate this instance.
/// </summary>
/// <returns>
/// A <see cref="Task"/> representing the work performed.
/// </returns>
public override Task OnActivateAsync()
{
this.address = new Address { Kind = Kinds.ChatRoom, Id = this.GetPrimaryKey() };
this.log = (Logger)this.GetLogger(this.address.ToString());
this.State.Value.Messages = this.State.Value.Messages ?? new List<ChatMessage>();
this.State.Value.Participants = this.State.Value.Participants ?? new HashSet<Address>();
return base.OnActivateAsync();
}
/// <summary>
/// Closes this room.
/// </summary>
/// <param name="user">The caller.</param>
/// <returns>A <see cref="Task"/> representing the work performed.</returns>
public async Task Close(IUserGrain user)
{
this.ThrowIfNotInitialized();
await this.ThrowIfNotMemberOrAdmin(user);
this.State.Value.State = ChatRoomState.Closed;
this.log.Info("Closed.");
await this.State.WriteStateAsync();
}
/// <summary>
/// Returns the actor state.
/// </summary>
/// <param name="user">
/// The requesting user.
/// </param>
/// <returns>
/// The actor state.
/// </returns>
public async Task<ChatRoom> Get(IUserGrain user)
{
await this.ThrowIfNotMemberOrAdmin(user);
return this.State.Value;
}
/// <summary>
/// Sends the provided <paramref name="message"/> to the participants in this room.
/// </summary>
/// <param name="user">The user.</param>
/// <param name="message">The message.</param>
/// <returns>A <see cref="Task"/> representing the work performed.</returns>
public async Task SendMessage(IUserGrain user, string message)
{
this.ThrowIfNotInitialized();
this.ThrowIfClosed();
await this.ThrowIfNotMemberOrAdmin(user);
if (string.IsNullOrWhiteSpace(message))
{
return;
}
var now = DateTime.UtcNow;
var msg = new ChatMessage { Room = this.address, Time = now, Message = message, Sender = user.GetAddress() };
var recipients = this.State.Value.Participants;
this.State.Value.Messages.Add(msg);
await this.State.WriteStateAsync();
this.log.Info("Message: {0} {1}", user.GetAddress(), message);
foreach (var actor in recipients.Select(recipient => GrainFactory.GetGrain<IUserGrain>(recipient.Id)))
{
await actor.NewMessage(msg);
}
}
/// <summary>
/// Returns a value indicating whether this room was created.
/// </summary>
/// <param name="participants">The room participants.</param>
/// <param name="item">The item.</param>
/// <returns>A value indicating whether this room was created.</returns>
public async Task<bool> Create(HashSet<Address> participants, Item item)
{
if (this.State.Value.State != ChatRoomState.Uninitialized)
{
return false;
}
this.log.Info("Create({0}, {1})", string.Join(", ", participants), item);
this.State.Value.Participants = participants;
this.State.Value.State = ChatRoomState.Created;
this.State.Value.Item = item;
await this.State.WriteStateAsync();
return true;
}
/// <summary>
/// Removes persisted state for this instance.
/// </summary>
/// <param name="admin">
/// The administrator.
/// </param>
/// <returns>
/// A <see cref="Task"/> representing the work performed.
/// </returns>
public async Task RemoveState(IUserGrain admin)
{
this.log.Info("RemoveState({0})", admin.GetAddress());
await admin.ThrowIfNotAdmin();
await this.State.ClearStateAsync();
this.DeactivateOnIdle();
}
/// <summary>
/// The deactivate.
/// </summary>
/// <param name="admin">
/// The admin.
/// </param>
/// <returns>
/// A <see cref="Task"/> representing the work performed.
/// </returns>
public async Task Deactivate(IUserGrain admin)
{
this.log.Info("Deactivate({0})", admin.GetAddress());
await admin.ThrowIfNotAdmin();
this.DeactivateOnIdle();
}
/// <summary>
/// Attempts to repair this instance.
/// </summary>
/// <param name="admin">The caller.</param>
/// <returns>A <see cref="Task"/> representing the work performed.</returns>
public Task Repair(IUserGrain admin)
{
this.log.Info("Repair - no repair actions to take.");
return Task.FromResult(0);
}
/// <summary>
/// Handles grain deactivation.
/// </summary>
/// <returns>A <see cref="Task"/> representing the work performed.</returns>
public override Task OnDeactivateAsync()
{
this.log.Info("Deactivating.");
return base.OnDeactivateAsync();
}
/// <summary>
/// Sends a reminder to this instance.
/// </summary>
/// <param name="admin">The admin.</param>
/// <param name="reminderName">The reminder name.</param>
/// <returns>A <see cref="Task"/> representing the work performed.</returns>
public async Task Reminder(IUserGrain admin, string reminderName)
{
await admin.ThrowIfNotAdmin();
await this.ReceiveReminder(reminderName, new TickStatus());
}
/// <summary>
/// Handles a reminder.
/// </summary>
/// <param name="reminderName">
/// The reminder name.
/// </param>
/// <param name="tickStatus">
/// The tick status.
/// </param>
/// <returns>
/// A <see cref="Task"/> representing the work performed.
/// </returns>
public Task ReceiveReminder(string reminderName, TickStatus tickStatus)
{
return Task.FromResult(0);
}
/// <summary>
/// Throws an exception if the provided <paramref name="user"/> is not a participant.
/// </summary>
/// <param name="user">
/// The user.
/// </param>
/// <returns>
/// A <see cref="Task"/> representing the work performed.
/// </returns>
private async Task ThrowIfNotMemberOrAdmin(IUserGrain user)
{
var userAddress = user.GetAddress();
if (this.State.Value.Participants != null && !this.State.Value.Participants.Contains(userAddress))
{
if (await user.IsAdmin())
{
return;
}
this.log.Warn("{0} is not a participant.", new object[] { userAddress });
throw ExceptionUtil.Create(
ErrorCode.AccessDenied,
"Sender (" + userAddress + ") is not a participant.",
log: this.log);
}
}
/// <summary>
/// Throws an exception if the room is not initialized.
/// </summary>
/// <exception cref="InvalidOperationException">
/// The room is not initialized.
/// </exception>
private void ThrowIfNotInitialized()
{
if (this.State.Value.State == ChatRoomState.Uninitialized)
{
throw ExceptionUtil.Create(ErrorCode.Invalid, "Instance not yet initialized.", log: this.log);
}
}
/// <summary>
/// Throws an exception if the room is closed..
/// </summary>
private void ThrowIfClosed()
{
if (this.State.Value.State == ChatRoomState.Closed)
{
throw ExceptionUtil.Create(ErrorCode.Invalid, "Cannot send message to closed room.", log: this.log);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment