voicepresence, logserver and userpresence persist restarts now. ObservableConcurrentDictionary implementation added. #104

This commit is contained in:
Master Kwoth 2016-07-04 08:47:29 +02:00
parent 7695112232
commit 6d641431ab
6 changed files with 310 additions and 62 deletions

View File

@ -0,0 +1,204 @@
//--------------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// File: ObservableConcurrentDictionary.cs
//
//--------------------------------------------------------------------------
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Diagnostics;
using System.Threading;
namespace System.Collections.Concurrent
{
/// <summary>
/// Provides a thread-safe dictionary for use with data binding.
/// </summary>
/// <typeparam name="TKey">Specifies the type of the keys in this collection.</typeparam>
/// <typeparam name="TValue">Specifies the type of the values in this collection.</typeparam>
[DebuggerDisplay("Count={Count}")]
public class ObservableConcurrentDictionary<TKey, TValue> :
ICollection<KeyValuePair<TKey, TValue>>, IDictionary<TKey, TValue>,
INotifyCollectionChanged, INotifyPropertyChanged
{
private readonly SynchronizationContext _context;
private readonly ConcurrentDictionary<TKey, TValue> _dictionary;
/// <summary>
/// Initializes an instance of the ObservableConcurrentDictionary class.
/// </summary>
public ObservableConcurrentDictionary()
{
_context = AsyncOperationManager.SynchronizationContext;
_dictionary = new ConcurrentDictionary<TKey, TValue>();
}
/// <summary>Event raised when the collection changes.</summary>
public event NotifyCollectionChangedEventHandler CollectionChanged;
/// <summary>Event raised when a property on the collection changes.</summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Notifies observers of CollectionChanged or PropertyChanged of an update to the dictionary.
/// </summary>
private void NotifyObserversOfChange()
{
var collectionHandler = CollectionChanged;
var propertyHandler = PropertyChanged;
if (collectionHandler != null || propertyHandler != null)
{
_context.Post(s =>
{
if (collectionHandler != null)
{
collectionHandler(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
if (propertyHandler != null)
{
propertyHandler(this, new PropertyChangedEventArgs("Count"));
propertyHandler(this, new PropertyChangedEventArgs("Keys"));
propertyHandler(this, new PropertyChangedEventArgs("Values"));
}
}, null);
}
}
/// <summary>Attempts to add an item to the dictionary, notifying observers of any changes.</summary>
/// <param name="item">The item to be added.</param>
/// <returns>Whether the add was successful.</returns>
private bool TryAddWithNotification(KeyValuePair<TKey, TValue> item)
{
return TryAddWithNotification(item.Key, item.Value);
}
/// <summary>Attempts to add an item to the dictionary, notifying observers of any changes.</summary>
/// <param name="key">The key of the item to be added.</param>
/// <param name="value">The value of the item to be added.</param>
/// <returns>Whether the add was successful.</returns>
private bool TryAddWithNotification(TKey key, TValue value)
{
bool result = _dictionary.TryAdd(key, value);
if (result) NotifyObserversOfChange();
return result;
}
/// <summary>Attempts to remove an item from the dictionary, notifying observers of any changes.</summary>
/// <param name="key">The key of the item to be removed.</param>
/// <param name="value">The value of the item removed.</param>
/// <returns>Whether the removal was successful.</returns>
private bool TryRemoveWithNotification(TKey key, out TValue value)
{
bool result = _dictionary.TryRemove(key, out value);
if (result) NotifyObserversOfChange();
return result;
}
/// <summary>Attempts to add or update an item in the dictionary, notifying observers of any changes.</summary>
/// <param name="key">The key of the item to be updated.</param>
/// <param name="value">The new value to set for the item.</param>
/// <returns>Whether the update was successful.</returns>
private void UpdateWithNotification(TKey key, TValue value)
{
_dictionary[key] = value;
NotifyObserversOfChange();
}
#region ICollection<KeyValuePair<TKey,TValue>> Members
void ICollection<KeyValuePair<TKey, TValue>>.Add(KeyValuePair<TKey, TValue> item)
{
TryAddWithNotification(item);
}
void ICollection<KeyValuePair<TKey, TValue>>.Clear()
{
((ICollection<KeyValuePair<TKey, TValue>>)_dictionary).Clear();
NotifyObserversOfChange();
}
bool ICollection<KeyValuePair<TKey, TValue>>.Contains(KeyValuePair<TKey, TValue> item)
{
return ((ICollection<KeyValuePair<TKey, TValue>>)_dictionary).Contains(item);
}
void ICollection<KeyValuePair<TKey, TValue>>.CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
{
((ICollection<KeyValuePair<TKey, TValue>>)_dictionary).CopyTo(array, arrayIndex);
}
int ICollection<KeyValuePair<TKey, TValue>>.Count {
get { return ((ICollection<KeyValuePair<TKey, TValue>>)_dictionary).Count; }
}
bool ICollection<KeyValuePair<TKey, TValue>>.IsReadOnly {
get { return ((ICollection<KeyValuePair<TKey, TValue>>)_dictionary).IsReadOnly; }
}
bool ICollection<KeyValuePair<TKey, TValue>>.Remove(KeyValuePair<TKey, TValue> item)
{
TValue temp;
return TryRemoveWithNotification(item.Key, out temp);
}
#endregion
#region IEnumerable<KeyValuePair<TKey,TValue>> Members
IEnumerator<KeyValuePair<TKey, TValue>> IEnumerable<KeyValuePair<TKey, TValue>>.GetEnumerator()
{
return ((ICollection<KeyValuePair<TKey, TValue>>)_dictionary).GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return ((ICollection<KeyValuePair<TKey, TValue>>)_dictionary).GetEnumerator();
}
#endregion
#region IDictionary<TKey,TValue> Members
public void Add(TKey key, TValue value)
{
TryAddWithNotification(key, value);
}
public bool ContainsKey(TKey key)
{
return _dictionary.ContainsKey(key);
}
public ICollection<TKey> Keys {
get { return _dictionary.Keys; }
}
public bool Remove(TKey key)
{
TValue temp;
return TryRemoveWithNotification(key, out temp);
}
public bool TryGetValue(TKey key, out TValue value)
{
return _dictionary.TryGetValue(key, out value);
}
public bool TryAdd(TKey key, TValue value)
{
return TryAddWithNotification(key, value);
}
public ICollection<TValue> Values {
get { return _dictionary.Values; }
}
public TValue this[TKey key] {
get { return _dictionary[key]; }
set { UpdateWithNotification(key, value); }
}
public bool TryRemove(TKey key, out TValue value)
{
return TryRemoveWithNotification(key, out value);
}
#endregion
}
}

View File

@ -82,6 +82,45 @@ namespace NadekoBot.Classes
}
}
[JsonProperty("LogChannel")]
private ulong? logServerChannel = null;
[JsonIgnore]
public ulong? LogServerChannel {
get { return logServerChannel; }
set {
logServerChannel = value;
if (!SpecificConfigurations.Instantiated) return;
OnPropertyChanged();
}
}
[JsonProperty("LogPresenceChannel")]
private ulong? logPresenceChannel = null;
[JsonIgnore]
public ulong? LogPresenceChannel {
get { return logPresenceChannel; }
set {
logPresenceChannel = value;
if (!SpecificConfigurations.Instantiated) return;
OnPropertyChanged();
}
}
[JsonIgnore]
private ObservableConcurrentDictionary<ulong, ulong> voiceChannelLog;
public ObservableConcurrentDictionary<ulong, ulong> VoiceChannelLog {
get { return voiceChannelLog; }
set {
voiceChannelLog = value;
if (value != null)
voiceChannelLog.CollectionChanged += (s, e) =>
{
if (!SpecificConfigurations.Instantiated) return;
OnPropertyChanged();
};
}
}
[JsonIgnore]
private ObservableCollection<ulong> listOfSelfAssignableRoles;
public ObservableCollection<ulong> ListOfSelfAssignableRoles {
@ -110,7 +149,6 @@ namespace NadekoBot.Classes
[JsonIgnore]
private ObservableCollection<ulong> generateCurrencyChannels;
public ObservableCollection<ulong> GenerateCurrencyChannels {
get { return generateCurrencyChannels; }
set {
@ -124,9 +162,6 @@ namespace NadekoBot.Classes
}
}
[JsonIgnore]
private ObservableCollection<StreamNotificationConfig> observingStreams;
[JsonIgnore]
private bool autoDeleteMessagesOnCommand = false;
public bool AutoDeleteMessagesOnCommand {
@ -138,6 +173,9 @@ namespace NadekoBot.Classes
}
}
[JsonIgnore]
private ObservableCollection<StreamNotificationConfig> observingStreams;
public ObservableCollection<StreamNotificationConfig> ObservingStreams {
get { return observingStreams; }
set {
@ -167,6 +205,7 @@ namespace NadekoBot.Classes
ListOfSelfAssignableRoles = new ObservableCollection<ulong>();
ObservingStreams = new ObservableCollection<StreamNotificationConfig>();
GenerateCurrencyChannels = new ObservableCollection<ulong>();
VoiceChannelLog = new ObservableConcurrentDictionary<ulong, ulong>();
}
public event PropertyChangedEventHandler PropertyChanged = delegate { SpecificConfigurations.Default.Save(); };

View File

@ -4,22 +4,12 @@ using NadekoBot.Classes;
using NadekoBot.Extensions;
using NadekoBot.Modules.Permissions.Classes;
using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Threading.Tasks;
namespace NadekoBot.Modules.Administration.Commands
{
internal class LogCommand : DiscordCommand
{
//server-channel
private readonly ConcurrentDictionary<ulong, ulong> logs = new ConcurrentDictionary<ulong, ulong>();
//server-channel
private readonly ConcurrentDictionary<ulong, ulong> loggingPresences = new ConcurrentDictionary<ulong, ulong>();
//channel-channel
private readonly ConcurrentDictionary<ulong, ulong> voiceChannelLog = new ConcurrentDictionary<ulong, ulong>();
private string prettyCurrentTime => $"【{DateTime.Now:HH:mm:ss}】";
public LogCommand(DiscordModule module) : base(module)
@ -61,8 +51,8 @@ namespace NadekoBot.Modules.Administration.Commands
{
try
{
ulong chId;
if (!logs.TryGetValue(e.Server.Id, out chId))
var chId = SpecificConfigurations.Default.Of(e.Server.Id).LogServerChannel;
if (chId == null)
return;
Channel ch;
if ((ch = e.Server.TextChannels.Where(tc => tc.Id == chId).FirstOrDefault()) == null)
@ -82,8 +72,8 @@ namespace NadekoBot.Modules.Administration.Commands
{
try
{
ulong chId;
if (!logs.TryGetValue(e.Server.Id, out chId))
var chId = SpecificConfigurations.Default.Of(e.Server.Id).LogServerChannel;
if (chId == null)
return;
Channel ch;
if ((ch = e.Server.TextChannels.Where(tc => tc.Id == chId).FirstOrDefault()) == null)
@ -97,8 +87,8 @@ namespace NadekoBot.Modules.Administration.Commands
{
try
{
ulong chId;
if (!logs.TryGetValue(e.Server.Id, out chId))
var chId = SpecificConfigurations.Default.Of(e.Server.Id).LogServerChannel;
if (chId == null)
return;
Channel ch;
if ((ch = e.Server.TextChannels.Where(tc => tc.Id == chId).FirstOrDefault()) == null)
@ -112,8 +102,8 @@ namespace NadekoBot.Modules.Administration.Commands
{
try
{
ulong chId;
if (!logs.TryGetValue(e.Server.Id, out chId))
var chId = SpecificConfigurations.Default.Of(e.Server.Id).LogServerChannel;
if (chId == null)
return;
Channel ch;
if ((ch = e.Server.TextChannels.Where(tc => tc.Id == chId).FirstOrDefault()) == null)
@ -127,8 +117,8 @@ namespace NadekoBot.Modules.Administration.Commands
{
try
{
ulong chId;
if (!logs.TryGetValue(e.Server.Id, out chId))
var chId = SpecificConfigurations.Default.Of(e.Server.Id).LogServerChannel;
if (chId == null)
return;
Channel ch;
if ((ch = e.Server.TextChannels.Where(tc => tc.Id == chId).FirstOrDefault()) == null)
@ -142,8 +132,8 @@ namespace NadekoBot.Modules.Administration.Commands
{
try
{
ulong chId;
if (!logs.TryGetValue(e.Server.Id, out chId))
var chId = SpecificConfigurations.Default.Of(e.Server.Id).LogServerChannel;
if (chId == null)
return;
Channel ch;
if ((ch = e.Server.TextChannels.Where(tc => tc.Id == chId).FirstOrDefault()) == null)
@ -157,8 +147,8 @@ namespace NadekoBot.Modules.Administration.Commands
{
try
{
ulong chId;
if (!logs.TryGetValue(e.Server.Id, out chId))
var chId = SpecificConfigurations.Default.Of(e.Server.Id).LogServerChannel;
if (chId == null)
return;
Channel ch;
if ((ch = e.Server.TextChannels.Where(tc => tc.Id == chId).FirstOrDefault()) == null)
@ -168,29 +158,14 @@ namespace NadekoBot.Modules.Administration.Commands
catch { }
}
public Func<CommandEventArgs, Task> DoFunc() => async e =>
{
ulong chId;
if (!logs.TryRemove(e.Server.Id, out chId))
{
logs.TryAdd(e.Server.Id, e.Channel.Id);
await e.Channel.SendMessage($"❗**I WILL BEGIN LOGGING SERVER ACTIVITY IN THIS CHANNEL**❗").ConfigureAwait(false);
return;
}
Channel ch;
if ((ch = e.Server.TextChannels.Where(tc => tc.Id == chId).FirstOrDefault()) == null)
return;
await e.Channel.SendMessage($"❗**NO LONGER LOGGING IN {ch.Mention} CHANNEL**❗").ConfigureAwait(false);
};
private async void MsgRecivd(object sender, MessageEventArgs e)
{
try
{
if (e.Server == null || e.Channel.IsPrivate || e.User.Id == NadekoBot.Client.CurrentUser.Id)
return;
ulong chId;
if (!logs.TryGetValue(e.Server.Id, out chId) || e.Channel.Id == chId)
var chId = SpecificConfigurations.Default.Of(e.Server.Id).LogServerChannel;
if (chId == null || e.Channel.Id == chId)
return;
Channel ch;
if ((ch = e.Server.TextChannels.Where(tc => tc.Id == chId).FirstOrDefault()) == null)
@ -217,8 +192,8 @@ namespace NadekoBot.Modules.Administration.Commands
{
if (e.Server == null || e.Channel.IsPrivate || e.User?.Id == NadekoBot.Client.CurrentUser.Id)
return;
ulong chId;
if (!logs.TryGetValue(e.Server.Id, out chId) || e.Channel.Id == chId)
var chId = SpecificConfigurations.Default.Of(e.Server.Id).LogServerChannel;
if (chId == null || e.Channel.Id == chId)
return;
Channel ch;
if ((ch = e.Server.TextChannels.Where(tc => tc.Id == chId).FirstOrDefault()) == null)
@ -244,8 +219,8 @@ namespace NadekoBot.Modules.Administration.Commands
{
if (e.Server == null || e.Channel.IsPrivate || e.User?.Id == NadekoBot.Client.CurrentUser.Id)
return;
ulong chId;
if (!logs.TryGetValue(e.Server.Id, out chId) || e.Channel.Id == chId)
var chId = SpecificConfigurations.Default.Of(e.Server.Id).LogServerChannel;
if (chId == null || e.Channel.Id == chId)
return;
Channel ch;
if ((ch = e.Server.TextChannels.Where(tc => tc.Id == chId).FirstOrDefault()) == null)
@ -260,10 +235,11 @@ $@"🕔`{prettyCurrentTime}` **Message** 📝 `#{e.Channel.Name}`
}
private async void UsrUpdtd(object sender, UserUpdatedEventArgs e)
{
var config = SpecificConfigurations.Default.Of(e.Server.Id);
try
{
ulong chId;
if (!loggingPresences.TryGetValue(e.Server.Id, out chId))
var chId = config.LogServerChannel;
if (chId != null)
{
Channel ch;
if ((ch = e.Server.TextChannels.Where(tc => tc.Id == chId).FirstOrDefault()) != null)
@ -289,11 +265,11 @@ $@"🕔`{prettyCurrentTime}` **Message** 📝 `#{e.Channel.Name}`
var notifyJoin = false;
if ((beforeVch != null || afterVch != null) && (beforeVch != afterVch)) // this means we need to notify for sure.
{
if (beforeVch != null && voiceChannelLog.TryGetValue(beforeVch.Id, out notifyChBeforeId) && (notifyChBefore = e.Before.Server.TextChannels.FirstOrDefault(tc => tc.Id == notifyChBeforeId)) != null)
if (beforeVch != null && config.VoiceChannelLog.TryGetValue(beforeVch.Id, out notifyChBeforeId) && (notifyChBefore = e.Before.Server.TextChannels.FirstOrDefault(tc => tc.Id == notifyChBeforeId)) != null)
{
notifyLeave = true;
}
if (afterVch != null && voiceChannelLog.TryGetValue(afterVch.Id, out notifyChAfterId) && (notifyChAfter = e.After.Server.TextChannels.FirstOrDefault(tc => tc.Id == notifyChAfterId)) != null)
if (afterVch != null && config.VoiceChannelLog.TryGetValue(afterVch.Id, out notifyChAfterId) && (notifyChAfter = e.After.Server.TextChannels.FirstOrDefault(tc => tc.Id == notifyChAfterId)) != null)
{
notifyJoin = true;
}
@ -315,8 +291,8 @@ $@"🕔`{prettyCurrentTime}` **Message** 📝 `#{e.Channel.Name}`
try
{
ulong chId;
if (!logs.TryGetValue(e.Server.Id, out chId))
var chId = SpecificConfigurations.Default.Of(e.Server.Id).LogServerChannel;
if (chId == null)
return;
Channel ch;
if ((ch = e.Server.TextChannels.Where(tc => tc.Id == chId).FirstOrDefault()) == null)
@ -377,17 +353,30 @@ $@"🕔`{prettyCurrentTime}` **Message** 📝 `#{e.Channel.Name}`
.Description("Toggles logging in this channel. Logs every message sent/deleted/edited on the server. **Bot Owner Only!**")
.AddCheck(SimpleCheckers.OwnerOnly())
.AddCheck(SimpleCheckers.ManageServer())
.Do(DoFunc());
.Do(async e =>
{
var chId = SpecificConfigurations.Default.Of(e.Server.Id).LogServerChannel;
if (chId == null)
{
SpecificConfigurations.Default.Of(e.Server.Id).LogServerChannel = e.Channel.Id;
await e.Channel.SendMessage($"❗**I WILL BEGIN LOGGING SERVER ACTIVITY IN THIS CHANNEL**❗").ConfigureAwait(false);
return;
}
Channel ch;
if ((ch = e.Server.TextChannels.Where(tc => tc.Id == chId).FirstOrDefault()) == null)
return;
await e.Channel.SendMessage($"❗**NO LONGER LOGGING IN {ch.Mention} CHANNEL**❗").ConfigureAwait(false);
});
cgb.CreateCommand(Module.Prefix + "userpresence")
.Description("Starts logging to this channel when someone from the server goes online/offline/idle.")
.AddCheck(SimpleCheckers.ManageServer())
.Do(async e =>
{
ulong chId;
if (!loggingPresences.TryRemove(e.Server.Id, out chId))
var chId = SpecificConfigurations.Default.Of(e.Server.Id).LogServerChannel;
if (chId == null)
{
loggingPresences.TryAdd(e.Server.Id, e.Channel.Id);
SpecificConfigurations.Default.Of(e.Server.Id).LogServerChannel = e.Channel.Id;
await e.Channel.SendMessage($"**User presence notifications enabled.**").ConfigureAwait(false);
return;
}
@ -402,11 +391,12 @@ $@"🕔`{prettyCurrentTime}` **Message** 📝 `#{e.Channel.Name}`
.Do(async e =>
{
var config = SpecificConfigurations.Default.Of(e.Server.Id);
if (e.GetArg("all")?.ToLower() == "all")
{
foreach (var voiceChannel in e.Server.VoiceChannels)
{
voiceChannelLog.TryAdd(voiceChannel.Id, e.Channel.Id);
config.VoiceChannelLog.TryAdd(voiceChannel.Id, e.Channel.Id);
}
await e.Channel.SendMessage("Started logging user presence for **ALL** voice channels!").ConfigureAwait(false);
return;
@ -418,9 +408,9 @@ $@"🕔`{prettyCurrentTime}` **Message** 📝 `#{e.Channel.Name}`
return;
}
ulong throwaway;
if (!voiceChannelLog.TryRemove(e.User.VoiceChannel.Id, out throwaway))
if (!config.VoiceChannelLog.TryRemove(e.User.VoiceChannel.Id, out throwaway))
{
voiceChannelLog.TryAdd(e.User.VoiceChannel.Id, e.Channel.Id);
config.VoiceChannelLog.TryAdd(e.User.VoiceChannel.Id, e.Channel.Id);
await e.Channel.SendMessage($"`Logging user updates for` {e.User.VoiceChannel.Mention} `voice channel.`").ConfigureAwait(false);
}
else

View File

@ -105,6 +105,21 @@ namespace NadekoBot.Modules.Music
}
});
//cgb.CreateCommand("soundcloudqueue")
// .Alias("sq")
// .Description("Queue a soundcloud song using keywords. Bot will join your voice channel." +
// "**You must be in a voice channel**.\n**Usage**: `!m sq Dream Of Venice`")
// .Parameter("query", ParameterType.Unparsed)
// .Do(async e =>
// {
// await QueueSong(e.Channel, e.User.VoiceChannel, e.GetArg("query")).ConfigureAwait(false);
// if (e.Server.CurrentUser.GetPermissions(e.Channel).ManageMessages)
// {
// await Task.Delay(10000).ConfigureAwait(false);
// await e.Message.Delete().ConfigureAwait(false);
// }
// });
cgb.CreateCommand("listqueue")
.Alias("lq")
.Description("Lists 15 currently queued songs per page. Default page is 1.\n**Usage**: `!m lq` or `!m lq 2`")