using SharpChat.Bans; using SharpChat.Channels; using SharpChat.Configuration; using SharpChat.Database; using SharpChat.DataProvider; using SharpChat.Events; using SharpChat.Messages; using SharpChat.Messages.Storage; using SharpChat.RateLimiting; using SharpChat.Sessions; using SharpChat.Users; using SharpChat.Users.Remote; using System; using System.Collections.Generic; using System.Linq; using System.Threading; namespace SharpChat { public class Context : IDisposable { public const int ID_LENGTH = 8; public string ServerId { get; } public EventDispatcher Events { get; } public SessionManager Sessions { get; } public UserManager Users { get; } public ChannelManager Channels { get; } public ChannelUserRelations ChannelUsers { get; } public MessageManager Messages { get; } public BanManager Bans { get; } public IDataProvider DataProvider { get; } public RateLimitManager RateLimiting { get; } public WelcomeMessage WelcomeMessage { get; } public ChatBot Bot { get; } = new(); private Timer BumpTimer { get; } public DateTimeOffset Created { get; } public Context(IConfig config, IDatabaseBackend databaseBackend, IDataProvider dataProvider) { if(config == null) throw new ArgumentNullException(nameof(config)); ServerId = RNG.NextString(ID_LENGTH); // maybe read this from the cfg instead Created = DateTimeOffset.Now; // read this from config definitely DatabaseWrapper db = new(databaseBackend ?? throw new ArgumentNullException(nameof(databaseBackend))); IMessageStorage msgStore = db.IsNullBackend ? new MemoryMessageStorage() : new ADOMessageStorage(db); Events = new EventDispatcher(); DataProvider = dataProvider ?? throw new ArgumentNullException(nameof(dataProvider)); Users = new UserManager(Events); Sessions = new SessionManager(Events, Users, config.ScopeTo(@"sessions"), ServerId); Messages = new MessageManager(Events, msgStore, config.ScopeTo(@"messages")); Channels = new ChannelManager(Events, config, Bot); ChannelUsers = new ChannelUserRelations(Events, Channels, Users, Sessions, Messages); Bans = new BanManager(Users, DataProvider.BanClient, DataProvider.UserClient, Events); RateLimiting = new RateLimitManager(config.ScopeTo(@"rateLimit")); WelcomeMessage = new WelcomeMessage(config.ScopeTo(@"welcome")); Events.AddEventHandler(Sessions); Events.ProtectEventHandler(Sessions); Events.AddEventHandler(Users); Events.ProtectEventHandler(Users); Events.AddEventHandler(Channels); Events.ProtectEventHandler(Channels); Events.AddEventHandler(ChannelUsers); Events.ProtectEventHandler(ChannelUsers); Events.AddEventHandler(Messages); Events.ProtectEventHandler(Messages); Events.StartProcessing(); Channels.UpdateChannels(); // Should probably not rely on Timers in the future BumpTimer = new Timer(e => { Logger.Write(@"Nuking dead sessions and bumping remote online status..."); Sessions.CheckTimeOut(); Sessions.GetActiveLocalSessions(sessions => { Dictionary> data = new(); foreach(ISession session in sessions) { if(!data.ContainsKey(session.User)) data.Add(session.User, new()); data[session.User].Add(session); } DataProvider.UserClient.BumpUsers( data.Select(kvp => new UserBumpInfo(kvp.Key, kvp.Value)), () => Logger.Debug(@"Successfully bumped remote online status!"), ex => { Logger.Write(@"Failed to bump remote online status."); Logger.Debug(ex); } ); }); }, null, TimeSpan.Zero, TimeSpan.FromMinutes(1)); } public void BroadcastMessage(string text) { Events.DispatchEvent(this, new BroadcastMessageEvent(Bot, text)); } [Obsolete(@"Use ChannelUsers.JoinChannel")] public void JoinChannel(IUser user, IChannel channel) { // handle in channelusers //channel.SendPacket(new UserChannelJoinPacket(user)); // send after join packet for v1 //user.SendPacket(new ContextClearPacket(channel, ContextClearMode.MessagesUsers)); // send after join //ChannelUsers.GetUsers(channel, u => user.SendPacket(new ContextUsersPacket(u.Except(new[] { user }).OrderByDescending(u => u.Rank)))); // send after join, maybe add a capability that makes this implicit? /*Messages.GetMessages(channel, m => { foreach(IMessage msg in m) user.SendPacket(new ContextMessagePacket(msg)); });*/ // should happen implicitly for v1 clients //user.ForceChannel(channel); } private bool IsDisposed; ~Context() => DoDispose(); public void Dispose() { DoDispose(); GC.SuppressFinalize(this); } private void DoDispose() { if (IsDisposed) return; IsDisposed = true; BumpTimer.Dispose(); Events.FinishProcessing(); } } }