Beispiel #1
0
        internal void GetCommonNewsAndSendToClient(DashboardClient p_client)
        {
            string rssFeedUrl = string.Format(@"https://www.cnbc.com/id/100003114/device/rss/rss.html");

            List <NewsItem> foundNewsItems = new List <NewsItem>();
            // try max 5 downloads to leave the tread for sure (call this method repeats continuosly)
            int retryCount = 0;

            while ((foundNewsItems.Count < 1) && (retryCount < 5))
            {
                foundNewsItems = ReadRSS(rssFeedUrl, NewsSource.CnbcRss, string.Empty);
                if (foundNewsItems.Count == 0)
                {
                    System.Threading.Thread.Sleep(m_sleepBetweenDnsMs.Key + m_random.Next(m_sleepBetweenDnsMs.Value));
                }
                retryCount++;
            }
            // AddFoundNews(0, foundNewsItems);
            // return NewsToString(m_newsMemory[0]);

            byte[] encodedMsg = Encoding.UTF8.GetBytes("QckfNews.CommonNews:" + Utils.CamelCaseSerialize(foundNewsItems));
            if (p_client.WsWebSocket != null && p_client.WsWebSocket !.State == WebSocketState.Open)
            {
                // to free up resources, send data only if either this is the active tool is this tool or if some seconds has been passed
                // OnConnectedWsAsync() sleeps for a while if not active tool.
                TimeSpan timeSinceConnect = DateTime.UtcNow - p_client.WsConnectionTime;
                if (p_client.ActivePage != ActivePage.QuickfolioNews && timeSinceConnect < DashboardClient.c_initialSleepIfNotActiveToolQn.Add(TimeSpan.FromMilliseconds(100)))
                {
                    return;
                }

                p_client.WsWebSocket.SendAsync(new ArraySegment <Byte>(encodedMsg, 0, encodedMsg.Length), WebSocketMessageType.Text, true, CancellationToken.None);
            }

            // foreach (var client in p_clients)        // List<DashboardClient> p_clients
            // {
            //     if (client.WsWebSocket != null && client.WsWebSocket!.State == WebSocketState.Open)
            //     {
            //         // to free up resources, send data only if either this is the active tool is this tool or if some seconds has been passed
            //         // OnConnectedWsAsync() sleeps for a while if not active tool.
            //         TimeSpan timeSinceConnect = DateTime.UtcNow - client.WsConnectionTime;
            //         if (client.ActivePage != ActivePage.QuickfolioNews && timeSinceConnect < DashboardClient.c_initialSleepIfNotActiveToolQn.Add(TimeSpan.FromMilliseconds(100)))
            //             continue;

            //         client.WsWebSocket.SendAsync(new ArraySegment<Byte>(encodedMsg, 0, encodedMsg.Length), WebSocketMessageType.Text, true, CancellationToken.None);
            //     }
            // }
        }
Beispiel #2
0
        internal void GetStockNewsAndSendToClient(DashboardClient p_client) // with 13 tickers, it can take 13 * 2 = 26seconds
        {
            foreach (string ticker in m_stockTickers)
            {
                byte[]? encodedMsgRss = null, encodedMsgBenzinga = null, encodedMsgTipranks = null;
                string rssFeedUrl = string.Format(@"https://feeds.finance.yahoo.com/rss/2.0/headline?s={0}&region=US&lang=en-US", ticker);
                var    rss        = ReadRSS(rssFeedUrl, NewsSource.YahooRSS, ticker);
                if (rss.Count > 0)
                {
                    encodedMsgRss = Encoding.UTF8.GetBytes("QckfNews.StockNews:" + Utils.CamelCaseSerialize(rss));
                }
                var benzinga = ReadBenzingaNews(ticker);
                if (benzinga.Count > 0)
                {
                    encodedMsgBenzinga = Encoding.UTF8.GetBytes("QckfNews.StockNews:" + Utils.CamelCaseSerialize(benzinga));
                }
                var tipranks = ReadTipranksNews(ticker);
                if (tipranks.Count > 0)
                {
                    encodedMsgTipranks = Encoding.UTF8.GetBytes("QckfNews.StockNews:" + Utils.CamelCaseSerialize(tipranks));
                }

                if (p_client.WsWebSocket != null && p_client.WsWebSocket !.State == WebSocketState.Open)
                {
                    // to free up resources, send data only if either this is the active tool is this tool or if some seconds has been passed
                    // OnConnectedWsAsync() sleeps for a while if not active tool.
                    TimeSpan timeSinceConnect = DateTime.UtcNow - p_client.WsConnectionTime;
                    if (p_client.ActivePage != ActivePage.QuickfolioNews && timeSinceConnect < DashboardClient.c_initialSleepIfNotActiveToolQn.Add(TimeSpan.FromMilliseconds(100)))
                    {
                        continue;
                    }

                    if (encodedMsgRss != null)
                    {
                        p_client.WsWebSocket.SendAsync(new ArraySegment <Byte>(encodedMsgRss, 0, encodedMsgRss.Length), WebSocketMessageType.Text, true, CancellationToken.None);
                    }
                    if (encodedMsgBenzinga != null)
                    {
                        p_client.WsWebSocket.SendAsync(new ArraySegment <Byte>(encodedMsgBenzinga, 0, encodedMsgBenzinga.Length), WebSocketMessageType.Text, true, CancellationToken.None);
                    }
                    if (encodedMsgTipranks != null)
                    {
                        p_client.WsWebSocket.SendAsync(new ArraySegment <Byte>(encodedMsgTipranks, 0, encodedMsgTipranks.Length), WebSocketMessageType.Text, true, CancellationToken.None);
                    }
                }
            }
        }
Beispiel #3
0
        public static async Task OnConnectedAsync(HttpContext context, WebSocket webSocket)
        {
            // context.Request comes as: 'wss://' + document.location.hostname + '/ws/dashboard?t=bav'
            var userEmailClaim = context?.User?.Claims?.FirstOrDefault(p => p.Type == @"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress");
            var email          = userEmailClaim?.Value ?? "*****@*****.**";

            string?activeToolAtConnectionInit = context !.Request.Query["t"];   // if "t" is not found, the empty StringValues casted to string as null

            if (activeToolAtConnectionInit == null)
            {
                activeToolAtConnectionInit = "mh";
            }
            if (!DashboardClient.c_urlParam2ActivePage.TryGetValue(activeToolAtConnectionInit, out ActivePage activePage))
            {
                activePage = ActivePage.Unknown;
            }

            // https://stackoverflow.com/questions/24450109/how-to-send-receive-messages-through-a-web-socket-on-windows-phone-8-using-the-c
            var msgObj = new HandshakeMessage()
            {
                Email = email
            };

            byte[] encodedMsg = Encoding.UTF8.GetBytes("OnConnected:" + Utils.CamelCaseSerialize(msgObj));
            if (webSocket.State == WebSocketState.Open)
            {
                await webSocket.SendAsync(new ArraySegment <Byte>(encodedMsg, 0, encodedMsg.Length), WebSocketMessageType.Text, true, CancellationToken.None);    //  takes 0.635ms
            }
            // create a connectionID based on client IP + connectionTime; the userID is the email as each user must be authenticated by an email.
            var clientIP = WsUtils.GetRequestIPv6(context !);                                                          // takes 0.346ms

            Utils.Logger.Info($"DashboardWs.OnConnectedAsync(), Connection from IP: {clientIP} with email '{email}'"); // takes 1.433ms
            var             thisConnectionTime = DateTime.UtcNow;
            DashboardClient?client             = null;

            lock (DashboardClient.g_clients)    // find client from the same IP, assuming connection in the last 2000ms
            {
                // client = DashboardClient.g_clients.Find(r => r.ClientIP == clientIP && (thisConnectionTime - r.WsConnectionTime).TotalMilliseconds < 2000);
                // if (client == null)
                // {
                client = new DashboardClient(clientIP, email);
                DashboardClient.g_clients.Add(client);        // takes 0.004ms
                //}
                client.WsConnectionTime = thisConnectionTime; // used by the other (secondary) connection to decide whether to create a new g_clients item.
                client.WsWebSocket      = webSocket;
                client.WsHttpContext    = context;
                client.ActivePage       = activePage;
            }

            User?user = MemDb.gMemDb.Users.FirstOrDefault(r => r.Email == client !.UserEmail);

            if (user == null)
            {
                Utils.Logger.Error($"Error. UserEmail is not found among MemDb users '{client!.UserEmail}'.");
                return;
            }

            client !.OnConnectedWsAsync_MktHealth(activePage == ActivePage.MarketHealth, user);
            client !.OnConnectedWsAsync_BrAccViewer(activePage == ActivePage.BrAccViewer, user);
            client !.OnConnectedWsAsync_QckflNews(activePage == ActivePage.QuickfolioNews);
        }
Beispiel #4
0
        public static void Main(string[] args)   // entry point Main cannot be flagged as async, because at first await, Main thread would go back to Threadpool, but that terminates the Console app
        {
            string appName      = System.Reflection.MethodBase.GetCurrentMethod()?.ReflectedType?.Namespace ?? "UnknownNamespace";
            string systemEnvStr = $"(v1.0.15,{Utils.RuntimeConfig() /* Debug | Release */},CLR:{System.Environment.Version},{System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription},OS:{System.Environment.OSVersion},usr:{System.Environment.UserName},CPU:{System.Environment.ProcessorCount},ThId-{Thread.CurrentThread.ManagedThreadId})";

            Console.WriteLine($"Hi {appName}.{systemEnvStr}");
            gLogger.Info($"********** Main() START {systemEnvStr}");
            // Setting Console.Title
            // on Linux use it only in GUI mode. It works with graphical Xterm in VNC, but with 'screen' or with Putty it is buggy and after this, the next 200 characters are not written to console.
            // Future work if needed: bring a flag to use it in string[] args, but by default, don't set Title on Linux
            if (Utils.RunningPlatform() != Platform.Linux)      // https://stackoverflow.com/questions/47059468/get-or-set-the-console-title-in-linux-and-macosx-with-net-core
            {
                Console.Title = $"{appName} v1.0.15";           // "SqCoreWeb v1.0.15"
            }
            gHeartbeatTimer = new System.Threading.Timer((e) => // Heartbeat log is useful to find out when VM was shut down, or when the App crashed
            {
                Utils.Logger.Info($"**g_nHeartbeat: {gNheartbeat} (at every {cHeartbeatTimerFrequencyMinutes} minutes)");
                gNheartbeat++;
            }, null, TimeSpan.FromMinutes(0.5), TimeSpan.FromMinutes(cHeartbeatTimerFrequencyMinutes));


            string sensitiveConfigFullPath = Utils.SensitiveConfigFolderPath() + $"SqCore.WebServer.{appName}.NoGitHub.json";
            string systemEnvStr2           = $"Current working directory of the app: '{Directory.GetCurrentDirectory()}',{Environment.NewLine}SensitiveConfigFullPath: '{sensitiveConfigFullPath}'";

            gLogger.Info(systemEnvStr2);

            var builder = new ConfigurationBuilder()
                          .SetBasePath(Directory.GetCurrentDirectory())                          // GetCurrentDirectory() is the folder of the '*.csproj'.
                          .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) // no need to copy appsettings.json to the sub-directory of the EXE.
                          .AddJsonFile(sensitiveConfigFullPath, optional: true, reloadOnChange: true);

            //.AddUserSecrets<Program>()    // Used mostly in Development only, not in Production. Stored in a JSON configuration file in a system-protected user profile folder on the local machine. (e.g. user's %APPDATA%\Microsoft\UserSecrets\), the secret values aren't encrypted, but could be in the future.
            // do we need it?: No. Sensitive files are in separate folders, not up on GitHub. If server is not hacked, we don't care if somebody who runs the code can read the settings file. Also, scrambling secret file makes it more difficult to change it realtime.
            //.AddEnvironmentVariables();   // not needed in general. We dont' want to clutter op.sys. environment variables with app specific values.
            Utils.Configuration       = builder.Build();
            Utils.MainThreadIsExiting = new ManualResetEventSlim(false);
            // HealthMonitorMessage.InitGlobals(ServerIp.HealthMonitorPublicIp, ServerIp.DefaultHealthMonitorServerPort);       // until HealthMonitor runs on the same Server, "localhost" is OK

            Email.SenderName      = Utils.Configuration["Emails:HQServer"];
            Email.SenderPwd       = Utils.Configuration["Emails:HQServerPwd"];
            PhoneCall.TwilioSid   = Utils.Configuration["PhoneCall:TwilioSid"];
            PhoneCall.TwilioToken = Utils.Configuration["PhoneCall:TwilioToken"];
            PhoneCall.PhoneNumbers[Caller.Gyantal]  = Utils.Configuration["PhoneCall:PhoneNumberGyantal"];
            PhoneCall.PhoneNumbers[Caller.Charmat0] = Utils.Configuration["PhoneCall:PhoneNumberCharmat0"];

            StrongAssert.g_strongAssertEvent           += StrongAssertMessageSendingEventHandler;
            AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(AppDomain_BckgThrds_UnhandledException);
            TaskScheduler.UnobservedTaskException      += TaskScheduler_UnobservedTaskException; // Occurs when a faulted task's unobserved exception is about to trigger exception which, by default, would terminate the process.

            try
            {
                // 1. PreInit services. They might add callbacks to MemDb's events.
                DashboardClient.PreInit();    // services add handlers to the MemDb.EvMemDbInitialized event.

                // 2. Init services
                var redisConnString = (Utils.RunningPlatform() == Platform.Windows) ? Utils.Configuration["ConnectionStrings:RedisDefault"] : Utils.Configuration["ConnectionStrings:RedisLinuxLocalhost"];
                int redisDbIndex    = 0;                              // DB-0 is ProductionDB. DB-1+ can be used for Development when changing database schema, so the Production system can still work on the ProductionDB
                var db = new Db(redisConnString, redisDbIndex, null); // mid-level DB wrapper above low-level DB
                MemDb.gMemDb.Init(db);                                // high level DB used by functionalities
                BrokersWatcher.gWatcher.Init();

                Caretaker.gCaretaker.Init("SqCoreServer", Utils.Configuration["Emails:ServiceSupervisors"], p_needDailyMaintenance: true, TimeSpan.FromHours(2));
                SqTaskScheduler.gTaskScheduler.Init();

                Services_Init();

                // 3. Run services.
                // Create a dedicated thread for a single task that is running for the lifetime of my application.
                KestrelWebServer_Run(args);

                string userInput = string.Empty;
                do
                {
                    userInput = DisplayMenuAndExecute().Result;  // we cannot 'await' it, because Main thread would terminate, which would close the whole Console app.
                } while (userInput != "UserChosenExit" && userInput != "ConsoleIsForcedToShutDown");
            }
            catch (Exception e)
            {
                gLogger.Error(e, $"CreateHostBuilder(args).Build().Run() exception.");
                if (e is System.Net.Sockets.SocketException)
                {
                    gLogger.Error("Linux. See 'Allow non-root process to bind to port under 1024.txt'. If Dotnet.exe was updated, it lost privilaged port. Try 'whereis dotnet','sudo setcap 'cap_net_bind_service=+ep' /usr/share/dotnet/dotnet'.");
                }
                HealthMonitorMessage.SendAsync($"Exception in SqCoreWebsite.C#.MainThread. Exception: '{ e.ToStringWithShortenedStackTrace(1600)}'", HealthMonitorMessageID.SqCoreWebCsError).TurnAsyncToSyncTask();
            }

            Utils.MainThreadIsExiting.Set(); // broadcast main thread shutdown
            // 4. Try to gracefully stop services.
            KestrelWebServer_Stop();

            int timeBeforeExitingSec = 2;

            Console.WriteLine($"Exiting in {timeBeforeExitingSec}sec...");
            Thread.Sleep(TimeSpan.FromSeconds(timeBeforeExitingSec)); // give some seconds for long running background threads to quit

            // 5. Dispose service resources
            KestrelWebServer_Exit();
            Services_Exit();

            SqTaskScheduler.gTaskScheduler.Exit();
            Caretaker.gCaretaker.Exit();
            BrokersWatcher.gWatcher.Exit();
            MemDb.gMemDb.Exit();

            gLogger.Info("****** Main() END");
            NLog.LogManager.Shutdown();
        }